更多二分题目戳我
二分是一种思想,而非模板
在什么情况下使用二分?需要同时满足两个条件:上下界[L,R]确定,函数在[L,R]内是单调的。
二分法模板:
int left = ?, right = ?;//给L和R确定边界
while (left < right)
{
int ans;//ans记录答案
int mid = (left + right) >> 1;
if (check(mid)) {
ans = mid;//记录答案
...//移动left(或者right)
}
else ...//移动right(或者left)
}
所以,二分的难点就在于如何建立模型(寻找单调性作为mid)和check()条件(如何缩小边界),在写二分的时候,可能会用上其他的算法或者数据结构,比如在DP中,或者图论中。
二分法的典型应用有:最大值最小化,最小值最大化。
最大值最小化(让最大值尽可能的小)
https://www.luogu.com.cn/problem/P1462
题目:
题目背景 在艾泽拉斯大陆上有一位名叫歪嘴哦的神奇术士,他是部落的中坚力量 有一天他醒来后发现自己居然到了联盟的主城暴风城 在被众多联盟的士兵攻击后,他决定逃回自己的家乡奥格瑞玛 题目描述 在艾泽拉斯,有n个城市。编号为1,2,3,...,n。 城市之间有m条双向的公路,连接着两个城市,从某个城市到另一个城市,会遭到联盟的攻击,进而损失一定的血量。 每次经过一个城市,都会被收取一定的过路费(包括起点和终点)。路上并没有收费站。 假设1为暴风城,n为奥格瑞玛,而他的血量最多为b,出发时他的血量是满的。 歪嘴哦不希望花很多钱,他想知道,在可以到达奥格瑞玛的情况下,他所经过的所有城市中最多的一次收取的费用的最小值是多少。 输入格式 第一行3个正整数,n,m,b。分别表示有n个城市,m条公路,歪嘴哦的血量为b。 接下来有n行,每行1个正整数,fi。表示经过城市i,需要交费fi元。 再接下来有m行,每行3个正整数,ai,bi,ci(1<=ai,bi<=n)。表示城市ai和城市bi之间有一条公路,如果从城市ai到城市bi,
或者从城市bi到城市ai,会损失ci的血量。 输出格式 仅一个整数,表示歪嘴哦交费最多的一次的最小值。 如果他无法到达奥格瑞玛,输出AFK。 输入输出样例 输入 #1复制 4 4 8 8 5 6 10 2 1 2 2 4 1 1 3 4 3 4 3 输出 #1复制 10 说明/提示 对于60%的数据,满足n≤200,m≤10000,b≤200 对于100%的数据,满足n≤10000,m≤50000,b≤1000000000 对于100%的数据,满足ci≤1000000000,fi≤1000000000,可能有两条边连接着相同的城市。
分析:
题目说让求交费最多一次的最小值,是最大值最小化问题,我们二分这个费用。
check函数怎么写呢?
如果有比他大的,就换一条路走,走不通返回false
如果血量消耗完,返回false
但是这样显然是不对的,血量问题没有得到解决,血量够用不够用,
和路费的二分没有与之相匹配的单调性。
----------------------------------------------
我们换种思路来求,还是以路费作为二分的依据。
在所有的路径中,总有一个点的点权最大为x[]
我们要找到x[]中最小的那个x
我们将点权按照从小到大的顺序排个序,然后二分点权
check函数怎么写呢。
我们在二分到某个点权为x的点v的时候,将点权大于x的点删掉
在剩下的点中求一边最短路,如果不能从1走到n,返回false
如果求得的最短路的总边权大于等于我们的血量,返回false
然后都满足,返回true
小计:
这个题,我做了大概有三个小时,分析这个题的时候,并没有多花时间(当然,也是因为看了其他人的博客,找了思路)。思路是对的,但是自己的编码能力确实不行,中间一直在错。 一开始,我想的是将每个点的点权从小到大排个序,然后枚举下标,l=0,r=n+1 。然后如果r在1到n之间的话,就是有解,否则就是没解。但是我忽略了在dijkstra算法的时候筛去哪些大于mid的值的时候,cost的下标早就已经变化了。 还有就是dijkstra用的居然如此的生疏,最开始明明不需要将st标记为true的,我给标记为true了,这里的st数组的作用是如果这个点已经扩展过了其他所有点,那就给他标记上。并不是spfa算法中的标记是否在队列中。 还有一点就是,记录图的时候,用strust还是不熟悉啊,用错了都。
代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
typedef pair<int, int> PII;
const int N = 100010, inf = 0x3f3f3f3f;
int n, m, blood;
int val[N];
int h[N], e[N], ne[N], w[N], tot;
void add(int a, int b, int c)
{
e[++ tot] = b, w[tot] = c, ne[tot] = h[a], h[a] = tot;
}
int dist[N];
bool st[N];
bool check(int mid)
{
// printf("%d
", mid);
memset(st, 0, sizeof st);
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
priority_queue<PII, vector<PII>, greater<PII>> q;
q.push({0, 1});
while (q.size())
{
PII t = q.top();
q.pop();
int x = t.second;
if (st[x]) continue;
st[x] = true;
if (val[x] > mid) continue;
// printf("%d && %d && %d
", mid, x, val[x]);
for (int i = h[x]; ~i; i = ne[i])
{
int y = e[i], z = w[i];
if (val[y] > mid) continue;
if (dist[y] > dist[x] + z)
{
dist[y] = dist[x] + z;
q.push({dist[y], y});
}
}
}
return dist[n] < blood;
}
int main()
{
memset(h, -1, sizeof h);
scanf("%d%d%d", &n, &m, &blood);
for (int i = 1; i <= n; i ++ )
{
int x; scanf("%d", &x);
val[i] = x;
}
for (int i = 1; i <= m; i ++ )
{
int x, y, z; scanf("%d%d%d", &x, &y, &z);
add(x, y, z), add(y, x, z);
}
int l = 0, r = inf;
if (!check(r))
{
puts("AFK");
return 0;
}
while (l < r)
{
int mid = l + r >> 1;
// puts("--------------------------");
if (check(mid)) r = mid;
else l = mid + 1;
}
printf("%d
", r);
return 0;
}
最小值最大化(让最小值尽量大)
题目:
题目描述 Farmer John建造了一个有N(2<=N<=100,000)个隔间的牛棚,这些隔间分布在一条直线上,坐标是x1,...,xN (0<=xi<=1,000,000,000)。 他的C(2<=C<=N)头牛不满于隔间的位置分布,它们为牛棚里其他的牛的存在而愤怒。为了防止牛之间的互相打斗,Farmer John想把这些牛安置在指定的
隔间,所有牛中相邻两头的最近距离越大越好。那么,这个最大的最近距离是多少呢? 输入格式 第1行:两个用空格隔开的数字N和C。 第2~N+1行:每行一个整数,表示每个隔间的坐标。 输出格式 输出只有一行,即相邻两头牛最大的最近距离。 输入输出样例 输入 #1复制 5 3 1 2 8 4 9 输出 #1复制 3
分析:
要让最小值最大化,我们二分的就是这个最小值,如果所有牛的间距都大于等于mid,就是合法的(那mid就是最小值)
我们的目标就是让这个mid更大,二分就好
代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 5 using namespace std; 6 7 const int N = 100010, inf = 0x3f3f3f3f; 8 9 int n, c; 10 int a[N], b[N]; 11 12 bool check(int mid) 13 { 14 int cnt = 1, sum = 0; 15 for (int i = 1; i <= n - 1; i ++ ) 16 { 17 sum += b[i]; 18 if (sum >= mid) 19 { 20 cnt ++; 21 sum = 0; 22 } 23 } 24 25 return cnt >= c; 26 } 27 28 int main() 29 { 30 scanf("%d%d", &n, &c); 31 for (int i = 1; i <= n; i ++ ) 32 { 33 scanf("%d", &a[i]); 34 } 35 36 sort(a + 1, a + 1 + n); 37 for (int i = 1; i < n; i ++ ) 38 { 39 b[i] = a[i + 1] - a[i]; 40 } 41 42 int l = 0, r = inf; 43 while (l < r) 44 { 45 int mid = l + r + 1>> 1; 46 // printf("%d %d %d ", mid, l, r); 47 if (check(mid)) l = mid; 48 else r = mid - 1; 49 } 50 51 printf("%d ", r); 52 53 return 0; 54 }
三分法:
三分法常用来求解单峰函数或单谷函数的极值问题。
三分法通过将区间三等分(r - l) / 3,然后让mid1=l + (r - l) / 3, 让mid2 = r - (r - l) / 3;
如果这个函数是先增后减的,那么存在单峰。
如果f(mid1)<f(mid2)说明极值点v一定在mid1的右侧,如果f(mid1)>f(mid2)说明极值点一定在mid2的左侧。
三分法的时间复杂度是O(log(3))的。
https://www.luogu.com.cn/problem/P3382
题目:
题目描述
如题,给出一个 N 次函数,保证在范围 [l,r] 内存在一点 x,使得 [l,x] 上单调增,[x,r] 上单调减。试求出 x 的值。
输入格式
第一行一次包含一个正整数 N 和两个实数 l,r,含义如题目描述所示。
第二行包含 N + 1 个实数,从高到低依次表示该 N 次函数各项的系数。
输出格式
输出为一行,包含一个实数,即为 x 的值。若你的答案与标准答案的相对或绝对误差不超过 10^{-5} 则算正确。
输入输出样例
输入 #1复制
3 -0.9981 0.5
1 -3 -3 1
输出 #1复制
-0.41421
说明/提示
对于 100% 的数据,6<= N <=13,函数系数均在 [−100,100] 内且至多 15 位小数,∣l∣,∣r∣≤10 且至多 15 位小数.l <= r
【样例解释】
如图所示,红色段即为该函数 f(x) = x^3 - 3 x^2 - 3x + 1在区间 [-0.9981, 0.5]上的图像。
当 x = -0.41421 时图像位于最高点,故此时函数在 [l,x] 上单调增,[x,r] 上单调减,故 x=−0.41421,输出 −0.41421。
分析:
实数三分模板题。
代码:
#include <cstdio>
using namespace std;
int n;
double l, r;
double a[55];
double f(double mid)
{
double ans = 0;
for (int i = n; i >= 0; i -- )
{
ans = ans * mid + a[i];
}
return ans;
}
int main()
{
scanf("%d%lf%lf", &n, &l, &r);
for (int i = n; i >= 0; i -- )
scanf("%lf", &a[i]);
while (r - l > 1e-6)
{
double mid1 = l + (r - l) / 3, mid2 = r - (r - l) / 3;
if (f(mid1) > f(mid2)) r = mid2;
else l = mid1;
}
printf("%.5lf
", r);
return 0;
}
三分:
[曲线](https://ac.nowcoder.com/acm/contest/951/C)
题目:
链接:https://ac.nowcoder.com/acm/contest/951/C
来源:牛客网题目描述
明明做作业的时候遇到了n个二次函数Si(x)=ax2+bx+c,他突发奇想设计了一个新的函数F(x)=max{Si(x)},i=1…n
明明现在想求这个函数在[0,1000]的最小值,要求精确到小数点后四位,四舍五入。
输入描述:
输入包含T组数据,每组第一行一个整数n;
接下来n行,每行3个整数a,b,c,用来表示每个二次函数的3个系数。注意:二次函数有可能退化成一次。输出描述:
每组数据输出一行,表示新函数F(x)的在区间[0,1000]上的最小值。精确到小数点后四位,四舍五入。示例1输入
2 1 2 0 0 2 2 0 0 2 -4 2输出
0.0000 0.5000备注:
对于50%的数据,1≤n≤100;对于100%的数据,1≤T≤10,1≤n≤100000,0≤a≤100,0≤∣b∣≤5000,0≤∣c∣≤5000。
分析:
最大值最小问题
唯一明白的就是F函数就是一个单谷函数。。。明白了这一点,就OK了
代码:
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 100010, inf = 1 << 30;
const double eps = 1e-6;
int n;
double a[N], b[N], c[N];
double f(double x)
{
double ans = -inf;
for (int i = 1; i <= n; i ++ )
ans = max(ans, a[i] * x * x + b[i] * x + c[i]);
return ans;
}
void work()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++ )
{
scanf("%lf%lf%lf", &a[i], &b[i], &c[i]);
}
double l = 0, r = 1000;
while (r - l > eps)
{
double d = (r - l) / 3;
double mid1 = l + d, mid2 = r - d;
if (f(mid1) > f(mid2)) l = mid1;
else r = mid2;
}
printf("%.4lf
", f(r));
}
int main()
{
int T; scanf("%d", &T);
while (T -- )
{
work();
}
return 0;
}