二分专题

先贴一个二分的板子

传送门

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int erfen()
{
int l = 1, r = n, ans;
while (l <= r)
{
int mid = (l + r) >> 1;
if (check(mid))
{
ans = mid;
l = mid + 1;
}
else
r = mid - 1;
}
return ans;
}

一元三次方程

oj

[NOIP2001 提高组] 一元三次方程求解

题目描述

有形如:$a x^3 + b x^2 + c x + d = 0$ 这样的一个一元三次方程。给出该方程中各项的系数($a,b,c,d$ 均为实数),并约定该方程存在三个不同实根(根的范围在 $-100$ 至 $100$ 之间),且根与根之差的绝对值 $\ge 1$。要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后 $2$ 位。

提示:记方程 $f(x) = 0$,若存在 $2$ 个数 $x_1$ 和 $x_2$,且 $x_1 < x_2$,$f(x_1) \times f(x_2) < 0$,则在 $(x_1, x_2)$ 之间一定有一个根。

输入格式

一行,$4$ 个实数 $a, b, c, d$。

输出格式

一行,$3$ 个实根,从小到大输出,并精确到小数点后 $2$ 位。

样例 #1

样例输入 #1

1
1 -5 -4 20

样例输出 #1

1
-2.00 2.00 5.00

提示

【题目来源】

NOIP 2001 提高组第一题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <bits/stdc++.h>
using namespace std;
// https://www.luogu.com.cn/problem/P1024#submit
//带有小数的二分题
double a, b, c, d;
double test(double x)
{
return (a * x * x * x + b * x * x + c * x + d);
}
bool check(double x1, double mid)
{
if ((test(x1)) * (test(mid)) <= 0)
return true;
else
return false;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> a >> b >> c >> d;
for (double i = -100; i <= 100; i++)
{
int count = 0;
double x1, x2;
x1 = i;
x2 = i + 1;
if (!test(x1))
{
count++;
printf("%.2lf ", x1);
}
if (test(x1) * test(x2) < 0)
{
double l = x1, r = x2;
double ans = -1;
while (r - l >= 0.0001)
{
double mid = (r + l) / 2;
if (check(mid, r))
{
ans = mid;
l = mid;
}
else
{
ans = mid;
r = mid;
}
}
printf("%.2lf ", ans);
count++;
}
if (count == 3)
break;
}

return 0;
}

跳石头

[NOIP2015 提高组] 跳石头

题目背景

一年一度的“跳石头”比赛又要开始了!

题目描述

这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 $N$ 块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。

为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 $M$ 块岩石(不能移走起点和终点的岩石)。

输入格式

第一行包含三个整数 $L,N,M$,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。保证 $L \geq 1$ 且 $N \geq M \geq 0$。

接下来 $N$ 行,每行一个整数,第 $i$ 行的整数 $D_i( 0 < D_i < L)$, 表示第 $i$ 块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。

输出格式

一个整数,即最短跳跃距离的最大值。

样例 #1

样例输入 #1

1
2
3
4
5
6
25 5 2 
2
11
14
17
21

样例输出 #1

1
4

提示

输入输出样例 1 说明:将与起点距离为 $2$和 $14$ 的两个岩石移走后,最短的跳跃距离为 $4$(从与起点距离 $17$ 的岩石跳到距离 $21$ 的岩石,或者从距离 $21$ 的岩石跳到终点)。

另:对于 $20%$的数据,$0 ≤ M ≤ N ≤ 10$。

对于$50%$的数据,$0 ≤ M ≤ N ≤ 100$。

对于 $100%$的数据,$0 ≤ M ≤ N ≤ 50,000,1 ≤ L ≤ 1,000,000,000$。
oj

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <bits/stdc++.h>
using namespace std;
// https://www.luogu.com.cn/problem/P2678
// 模板二分,有点像22年蓝桥杯那道题
int dis[100000]; //距离起点距离
int L, M, N;
bool check(int ans)
{
int now = 0; //模拟跳的人,他现在处于原点,距离起点自然是0
int next = 0; //下一块石头
int cnt = 0; //假如要保证所有的最短距离是ans,要删去多少石头
while (next != N + 1)
{
next++;
if ((dis[next] - dis[now]) < ans)
{
cnt++;
}
else
{
now = next;
}
}
if (cnt <= M)
return true;
else
return false;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> L >> N >> M;
for (int i = 1; i <= N; i++)
{
int remp;
cin >> dis[i];
}
dis[N + 1] = L;
int l = 1, r = L;
int ans = -1;
while (l <= r)
{
int mid = (l + r) >> 1;
if (check(mid))
{
ans = mid;
l = mid + 1; //条件符合,扩大战果
}
else
{
r = mid - 1;
}
}
cout << ans;
return 0;
}

刺杀大使

刺杀大使

题目描述

某组织正在策划一起对某大使的刺杀行动。他们来到了使馆,准备完成此次刺杀,要进入使馆首先必须通过使馆前的防御迷阵。

迷阵由 $n\times m$ 个相同的小房间组成,每个房间与相邻四个房间之间有门可通行。在第 $n$ 行的 $m$ 个房间里有 $m$ 个机关,这些机关必须全部打开才可以进入大使馆。而第 $1$ 行的 $m$ 个房间有 $m$ 扇向外打开的门,是迷阵的入口。除了第 $1$ 行和第 $n$ 行的房间外,每个房间都被使馆的安保人员安装了激光杀伤装置,将会对进入房间的人造成一定的伤害。第 $i$ 行第 $j$ 列 造成的伤害值为 $p_{i,j}$(第 $1$ 行和第 $n$ 行的 $p$ 值全部为 $0$)。

现在某组织打算以最小伤害代价进入迷阵,打开全部机关,显然,他们可以选 择任意多的人从任意的门进入,但必须到达第 $n$ 行的每个房间。一个士兵受到的伤害值为他到达某个机关的路径上所有房间的伤害值中的最大值,整个部队受到的伤害值为所有士兵的伤害值中的最大值。现在,这个恐怖组织掌握了迷阵的情况,他们需要提前知道怎么安排士兵的行进路线可以使得整个部队的伤害值最小。

输入格式

第一行有两个整数 $n,m$,表示迷阵的大小。

接下来 $n$ 行,每行 $m$ 个数,第 $i$ 行第 $j$ 列的数表示 $p_{i,j}$。

输出格式

输出一个数,表示最小伤害代价。

样例 #1

样例输入 #1

1
2
3
4
5
4 2
0 0
3 5
2 4
0 0

样例输出 #1

1
3

提示

  • $50%$ 的数据,$n,m \leq 100$;
  • $100%$ 的数据,$n,m \leq 1000$,$p_{i,j} \leq 1000$。
    传送门
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    #include <bits/stdc++.h>
    using namespace std;
    // https://www.luogu.com.cn/problem/P1902
    // dfs+二分(思路挺清晰的)
    int n, m;
    int dirx[] = {0, 0, -1, 1};
    int diry[] = {1, -1, 0, 0};
    bool visit[1010][1010];
    int maze[1010][1010];
    bool flag;
    void dfs(int x, int y, int limit)
    {
    if (!(x <= n && x >= 1 && y >= 1 && y <= m))
    return;
    if (visit[x][y])
    return;
    visit[x][y] = 1;
    for (int i = 0; i < 4; i++)
    {
    int x1 = x + dirx[i];
    int y1 = y + diry[i];
    if (x1 == n)
    {
    flag = true;
    return;
    }
    if (maze[x1][y1] <= limit)
    dfs(x1, y1, limit);
    }
    }
    bool check(int ans)
    {
    flag = false;
    memset(visit, 0, sizeof(visit));
    dfs(1, 1, ans);
    return flag;
    }
    int main()
    {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> n >> m;
    int max1 = -1;
    for (int i = 1; i <= n; i++)
    {
    for (int j = 1; j <= m; j++)
    {
    cin >> maze[i][j];
    max1 = max(max1, maze[i][j]);
    }
    }
    int l = 0, r = max1;
    int ans = -1;
    while (l <= r)
    {
    int mid = (l + r) >> 1;
    if (check(mid))
    {
    ans = mid;
    r = mid - 1;
    }
    else
    {
    l = mid + 1;
    }
    }
    cout << ans;
    return 0;
    }

    聪明的质检员

    [NOIP2011 提高组] 聪明的质监员

题目描述

小T 是一名质量监督员,最近负责检验一批矿产的质量。这批矿产共有 $n$ 个矿石,从 $1$ 到 $n$ 逐一编号,每个矿石都有自己的重量 $w_i$ 以及价值 $v_i$ 。检验矿产的流程是:

1 、给定$ m$ 个区间 $[l_i,r_i]$;

2 、选出一个参数 $W$;

3 、对于一个区间 $[l_i,r_i]$,计算矿石在这个区间上的检验值 $y_i$:

$$y_i=\sum\limits_{j=l_i}^{r_i}[w_j \ge W] \times \sum\limits_{j=l_i}^{r_i}[w_j \ge W]v_j$$

其中 $j$ 为矿石编号。

这批矿产的检验结果 $y$ 为各个区间的检验值之和。即:$\sum\limits_{i=1}^m y_i$

若这批矿产的检验结果与所给标准值 $s$ 相差太多,就需要再去检验另一批矿产。小T 不想费时间去检验另一批矿产,所以他想通过调整参数 $W$ 的值,让检验结果尽可能的靠近标准值 $s$,即使得 $|s-y|$ 最小。请你帮忙求出这个最小值。

输入格式

第一行包含三个整数 $n,m,s$,分别表示矿石的个数、区间的个数和标准值。

接下来的 $n$ 行,每行两个整数,中间用空格隔开,第 $i+1$ 行表示 $i$ 号矿石的重量 $w_i$ 和价值 $v_i$。

接下来的 $m$ 行,表示区间,每行两个整数,中间用空格隔开,第 $i+n+1$ 行表示区间 $[l_i,r_i]$ 的两个端点 $l_i$ 和 $r_i$。注意:不同区间可能重合或相互重叠。

输出格式

一个整数,表示所求的最小值。

样例 #1

样例输入 #1

1
2
3
4
5
6
7
8
9
5 3 15 
1 5
2 5
3 5
4 5
5 5
1 5
2 4
3 3

样例输出 #1

1
10

提示

【输入输出样例说明】

当 $W$ 选 $4$ 的时候,三个区间上检验值分别为 $20,5 ,0$ ,这批矿产的检验结果为 $25$,此时与标准值 $S$ 相差最小为 $10$。

【数据范围】

对于 $10% $ 的数据,有 $1 ≤n ,m≤10$;

对于 $30% $的数据,有 $1 ≤n ,m≤500$ ;

对于 $50% $ 的数据,有 $ 1 ≤n ,m≤5,000$;

对于 $70%$ 的数据,有 $1 ≤n ,m≤10,000$ ;

对于 $100%$ 的数据,有 $ 1 ≤n ,m≤200,000$,$0 < w_i,v_i≤10^6$,$0 < s≤10^{12}$,$1 ≤l_i ≤r_i ≤n$ 。
oj

说实话题目都没看懂,题解先鸽了

借教室

[NOIP2012 提高组] 借教室

题目描述

在大学期间,经常需要租借教室。大到院系举办活动,小到学习小组自习讨论,都需要向学校申请借教室。教室的大小功能不同,借教室人的身份不同,借教室的手续也不一样。

面对海量租借教室的信息,我们自然希望编程解决这个问题。

我们需要处理接下来 $n$ 天的借教室信息,其中第 $i$ 天学校有 $r_i$ 个教室可供租借。共有 $m$ 份订单,每份订单用三个正整数描述,分别为 $d_j,s_j,t_j$,表示某租借者需要从第 $s_j$ 天到第 $t_j$ 天租借教室(包括第 $s_j$ 天和第 $t_j$ 天),每天需要租借 $d_j$ 个教室。

我们假定,租借者对教室的大小、地点没有要求。即对于每份订单,我们只需要每天提供 $d_j$ 个教室,而它们具体是哪些教室,每天是否是相同的教室则不用考虑。

借教室的原则是先到先得,也就是说我们要按照订单的先后顺序依次为每份订单分配教室。如果在分配的过程中遇到一份订单无法完全满足,则需要停止教室的分配,通知当前申请人修改订单。这里的无法满足指从第 $s_j$ 天到第 $t_j$ 天中有至少一天剩余的教室数量不足 $d_j$ 个。

现在我们需要知道,是否会有订单无法完全满足。如果有,需要通知哪一个申请人修改订单。

输入格式

第一行包含两个正整数 $n,m$,表示天数和订单的数量。

第二行包含 $n$ 个正整数,其中第 $i$ 个数为 $r_i$,表示第 $i$ 天可用于租借的教室数量。

接下来有 $m$ 行,每行包含三个正整数 $d_j,s_j,t_j$,表示租借的数量,租借开始、结束分别在第几天。

每行相邻的两个数之间均用一个空格隔开。天数与订单均用从 $1$ 开始的整数编号。

输出格式

如果所有订单均可满足,则输出只有一行,包含一个整数 $0$。否则(订单无法完全满足)

输出两行,第一行输出一个负整数 $-1$,第二行输出需要修改订单的申请人编号。

样例 #1

样例输入 #1

1
2
3
4
5
4 3 
2 5 4 3
2 1 3
3 2 4
4 2 4

样例输出 #1

1
2
-1 
2

提示

【输入输出样例说明】

第 $1 $份订单满足后,$4 $天剩余的教室数分别为 $0,3,2,3$。第 $2$ 份订单要求第 $2 $天到第 $4$ 天每天提供$ 3 $个教室,而第 $3$ 天剩余的教室数为$ 2$,因此无法满足。分配停止,通知第$2$ 个申请人修改订单。

【数据范围】

对于10%的数据,有$1≤ n,m≤ 10$;

对于30%的数据,有$1≤ n,m≤1000$;

对于 70%的数据,有$1 ≤ n,m ≤ 10^5$;

对于 100%的数据,有$1 ≤ n,m ≤ 10^6,0 ≤ r_i,d_j≤ 10^9,1 ≤ s_j≤ t_j≤ n$。

NOIP 2012 提高组 第二天 第二题

2022.2.20 新增一组 hack 数据
oj

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include <bits/stdc++.h>
using namespace std;
// 差分+二分
// https://www.luogu.com.cn/problem/P1083
int n, m;
struct node
{
int s;
int e;
int c;
} ask[10000000];
int cf[10000000]; //差分数组
int rush[10000000];
int room[1000000]; //预留的房间数目
bool check(int tar)
{
memset(cf, 0, sizeof(cf));
memset(rush, 0, sizeof(rush));
for (int i = 1; i <= tar; i++)
{
cf[ask[i].s] += ask[i].c;
cf[ask[i].e + 1] -= ask[i].c;
}
for (int i = 1; i <= n; i++)
{
rush[i] = rush[i - 1] + cf[i];
if (rush[i] > room[i])
return false;
}
return true;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
int remp;
cin >> room[i];
}
for (int i = 1; i <= m; i++)
{
cin >> ask[i].c >> ask[i].s >> ask[i].e;
}
int l = 1, r = m;
int ans = -1;
if (check(m))
{
cout << 0 << endl;
return 0;
}
while (l <= r)
{
int mid = (l + r) >> 1;
if (!check(mid))
{
ans = mid;
r = mid - 1;
}
else
{
l = mid + 1;
}
}
cout << -1 << endl;
cout << ans ;
// int left = 1, right = m;
// while (left < right)
// {
// int mid = (left + right) / 2;
// if (check(mid))
// left = mid + 1;
// else
// right = mid;
// }
// cout << -1 << endl;
// cout << left;

return 0;
}

PS:洛谷22年2月更新了数据,第二十一个测试点要开long long才能过,我的代码的话把数组一千万改成一百万就能过了
什么?你问我为啥不把改过的代码传上来?那是因为我太懒了QAQ

自动刷题机

[SHOI2015]自动刷题机

题目背景

曾经发明了信号增幅仪的发明家 SHTSC 又公开了他的新发明:自动刷题机——一种可以自动 AC 题目的神秘装置。

题目描述

自动刷题机刷题的方式非常简单:首先会瞬间得出题目的正确做法,然后开始写程序。每秒,自动刷题机的代码生成模块会有两种可能的结果:

1.写了 $x$ 行代码
2.心情不好,删掉了之前写的 $y$ 行代码。(如果 $y$ 大于当前代码长度则相当于全部删除。)

对于一个 OJ,存在某个固定的正整数长度 $n$,一旦自动刷题机在某秒结束时积累了大于等于 $n$ 行的代码,它就会自动提交并 AC 此题,然后新建一个文件(即弃置之前的所有代码)并开始写下一题。SHTSC 在某个 OJ 上跑了一天的自动刷题机,得到了很多条关于写代码的日志信息。他突然发现自己没有记录这个 OJ 的 $n$ 究竟是多少。所幸他通过自己在 OJ 上的 Rank 知道了自动刷题机一共切了 $k$ 道题,希望你计算 $n$ 可能的最小值和最大值。

输入格式

第一行两个整数 $l , k$,表示刷题机的日志一共有 $l$ 行,一共了切了 $k$ 题。

接下来 $l$ 行,每行一个整数 $x_i$,依次表示每条日志。若 $x_i \geq 0$,则表示写了 $x_i$ 行代码,若 $x_i \lt 0$,则表示删除了 $-x_i$ 行代码。

输出格式

输出一行两个整数,分别表示 $n$ 可能的最小值和最大值。
如果这样的 $n$ 不存在,请输出一行一个整数 $-1$。

样例 #1

样例输入 #1

1
2
3
4
5
4 2
2
5
-3
9

样例输出 #1

1
3 7

提示

数据规模与约定

  • 对于 $20%$ 的数据,保证 $l \le 10$;
  • 对于 $40%$ 的数据,保证 $l \le 100$ ;
  • 对于 $60%$ 的数据,保证$l \le 2 \times 10^3$;
  • 对于 $100%$ 的数据,保证 $1 \leq l \le 10^5$,$-10^9 \le x_i \le 10^9$。
    oj
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    #include <bits/stdc++.h>
    using namespace std;
    // https://www.luogu.com.cn/problem/P4343
    long long gap[100010];
    long long l, k;
    long long check(long long tar)
    {
    long long cnt = 0;
    long long now = 0;
    for (int i = 1; i <= l; i++)
    {
    now += gap[i];
    if (now < 0)
    now = 0;
    if (now >= tar)
    {
    cnt++;
    now = 0;
    }
    }
    return cnt;
    }
    int main()
    {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> l >> k;
    for (int i = 1; i <= l; i++)
    {
    cin >> gap[i];
    }
    long long l = 1, r = 1e18;
    long long ans1 = -1, ans2 = -1;
    while (l <= r)
    {
    long long mid = (l + r) / 2;
    if (check(mid) <= k) //符合方向
    {
    if (check(mid) == k) //完全符合条件
    ans1 = mid;
    r = mid - 1; //找小的
    }
    else
    {
    l = mid + 1;
    }
    }
    l = 1, r = 1e18;
    while (l <= r)
    {
    long long mid = (l + r) / 2;
    if (check(mid) >= k) //符合方向?
    {
    if (check(mid) == k) //注意!!必须符合条件才能取等
    ans2 = mid;
    l = mid + 1; //找大的
    }
    else
    {
    r = mid - 1;
    }
    }

    if (ans1 == -1 || ans2 == -1)
    cout << -1 << endl;
    else
    cout << ans1 << " " << ans2 << endl;
    return 0;
    }
    这题挺典型的,把一个模型里求最大和求最小都囊括了,可以对比看看