〇、引子
二分是什么?
就是分而治之
举个例子,假设某一天你被 JC 了,幸运的是,机房后面有一个监控,它记录了你电脑上的界面,你想知道什么时候你被 JC ,那你应该怎么找呢? 你可以一秒一秒的查,这非常慢,但是入果你使用二分的思想,选择中间的时间,如果已经被 JC 了,就往前找,如果没有,就往后找,然后再取中间值,不停下去,就可以很快地找到这个时间。
一、简述
二分,大家应该都知道,就是通过折半查找来进行枚举。
而二分答案就是就相当于枚举答案,并判断答案是否合法,如果合法,就将答案进一步靠近,如果不合法,就往前判断。对于每个次判断,即使复杂度较高,也可以稳过。
大概就是这个思路:(绿色表示符合条件,灰色表示不符合)
复杂度? 不是显而易见的吗???
二、范围
二分答案可以用在什么地方呢?
显然,每次判断都会返回一个布尔值,这个值判断这个答案是大了还是小了,所以只有答案具有单调性的时候才可以用二分答案。还可以通过找“最大值最小”或“最小值最大”这种字眼来判断能不能使用二分。
三、模板
ll l=0,r=100000001,mid;//定义最小边界、最大边界。
while (l+1<r) {//判断结束。
mid=(l+r)/2;//找中间值。
if(f(mid)) l=mid;//移动左端点。
else r=mid;//移动右端点。
}
二分答案最重要的就是写判断函数,其中要尤其注意当判断答案的大小要注意等于的情况。
当然,你也可以在循环中就直接判断等于的情况。
四、例题
先来点简单的:
1.P2440 木材加工
求一个最大值并能在所有 中取出
段。
这个题可以二分 ,然后将每一个
都枚举一遍,取出每一个
能分出几个
并求出总和与
比较。如果大于
就说明
太小了,反之就是太大了。
核心代码:
bool f(ll ans) {
ll sum = 0;
for (int i=1;i<=n;i++) {
sum+=a[i]/ans;
}
return sum>=k;
}
ll l=0,r=100000001,mid;
while (l+1<r) {
mid=l+r>>1;
if(f(mid)) l=mid;
else r=mid;
}
cout<<l<<endl;
2. P2678 跳石头
在 中去掉
个点,使点的间隔的最小值最大,求这个最大值。
这个题我们看到了标志“间隔的最小值最大”,就说明可以直接二分答案。我们二分一个 ,并记录出间隔小于
的个数,最后与
比较,如果小于等于,就说明大了,反之就是小了。
核心代码:
bool F(int x){
ll i,k=0,ans=0;
for(i=1;i<=n;i++){
if(a[i]-k<x) ans++;
else k=a[i];
}
return ans<=m;
}
ll l=1,r=L+1,mid;
while(left+1<right){
mid=(left+right)/2;
if(F(mid)) l=mid;
else r=mid;
}
cout<<l;
稍微难一点的:
3.P3853 路标设置
在区间 [0,L] 中放置路标,使两个路标之间的差的最大值最小。(已经有一些路标)
又是最大值最小,继续二分,这个判断函数怎么写呢?对于每两个已经放置好的路标,我们只需要除以 就可以了,但如果正好整除,那就把总数减一。
核心代码:
bool F(int x){
int num=0;
for(int i=2;i<=n;i++){
if(a[i]-a[i-1]>=x){
num+=(a[i]-a[i-1])/x;
if((a[i]-a[i-1])%x==0)
num--;
}
}
return num>k;
}
再难点:
4.P1948 [USACO08JAN]Telephone Lines S
终于到蓝题了(
将 对电线杆相连,并接到第
和第 个电线杆上,其中 对是免费的,剩下的费用是最长的电线的长度,求最小值。
这个题要结合最短路。我们二分最小费用,然后对于每一个费用,求出 到
最小需要免费几条线,最后和
比较。
代码:
// Problem: P1948 [USACO08JAN]Telephone Lines S
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1948
// Memory Limit: 125 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define M 100005
#define INF 0x3f3f3f3f
int n,p,k;
int ft[M],nt[M],v[M],w[M],idx=1,cnt;
void add(int a,int b,int c){
v[idx]=b;
w[idx]=c;
nt[idx]=ft[a];
ft[a]=idx++;
}//邻接表存图
bool vis[M];
int dist[M];
int F(int x){
queue<int>q;
q.push(1);
for(int i=2;i<=n;i++) dist[i]=INF;
vis[1]=1;dist[1]=0;
while(!q.empty()){//SPFA
int h=q.front();
q.pop();
vis[h]=0;
for(int i=ft[h];~i;i=nt[i]){//枚举
int y=v[i],z=w[i];
if(z<=x) z=0;//判断
else z=1;
if(dist[y]>dist[h]+z){
dist[y]=dist[h]+z;
if(!vis[y]){
vis[y]=1;
q.push(y);
}
}
}
}
if(dist[n]==INF){
cout<<"-1";
return -1;//特判
}
return dist[n]<=k;
}
int main(){
memset(ft,-1,sizeof(ft));
cin>>n>>p>>k;
for(int i=1;i<=p;i++){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
add(b,a,c);
cnt+=c;//最大值是权值总和。
}
int l=0,r=cnt,mid;
while(l<=r){
mid=(l+r)/2;
if(F(mid)==0) l=mid+1;
else if(F(mid)==1) r=mid-1;
else return 0;//很笨的方法判断。
}
cout<<l;
return 0;
}
5.P1163 山
给出一个山(在坐标系中的一个分段函数),在山的某一部位安装一盏灯,求灯的y坐标最小值,使山的所有部位都能被灯照到。
第一眼看这个题目,可能会觉得这题要二分 $x$ 和 $y$。但是分析一下我们知道能放灯的地方只有所有直线的上方:
那么我们可以很快联想到要先把所有的直线解析式求出来。
那么我们定义一个 和
,存储的是第
条直线的解析式(
)。
然后呢?然后对于每一个 坐标,判断在最大的左端点
和最小的右端点
,就像这样:
(在高度为y的情况下,x=L 是最大的右端点,x=R 是最小的右端点)
最后判断,如果 就是不符合条件。
核心代码:
bool F(double x){
double L=-100005,R=100005;
for(int i=2;i<=n;i++){
if(k[i]<0) L=max(L,(x-b[i])/k[i]);
else if(k[i]>0) R=min(R,(x-b[i])/k[i]);
else if(x<b[i]) return 0;//特判k等于0的情况。
}
return L<=R;
}
6.CF492D Vanya and Computer Game
A 每秒攻击 次,B每秒攻击
次,第
个怪物被攻击
次就死了,问谁给了每个怪物最后一击。
这题实际上是比例的问题,A 打一次是 秒 B打一次是
秒,比例是
:
,浮点数不好计算,把二者乘以
,那么 A 打一次就是
秒,B打一次就是
秒。然后二分怪物被打死的时间
条件是
。
注意乘以之后数据范围的变化,已经超过题目所给的 了,所以二分枚举时间的时候要特别注意。
代码:
while(n--){
int t;
cin>>t;
ll l=-1,r=1e15,mid;
while(l+1<r){
mid=(l+r)/2;
if(mid/x+mid/y>=t) r=mid;
else l=mid;
}
if(r%x==0&&r%y==0) cout<<"Both"<<endl;
else if(r%x==0) cout<<"Vova"<<endl;
else cout<<"Vanya"<<endl;
}
7.P2218 [HAOI2007]覆盖问题
给出平面上 个点,求一个最小的
,使每个点都能被至少一个
的正方形覆盖,只有3个正方形。
这个题难点就在与如何判断是否符合条件。因为只有三个正方形,所以我们可以先判断第一个正方形,然后删除已经被覆盖的点,然后对于剩余的点求出第二个正方形,最后在求出第三个,之后用剩余的点与全部的点比较,最后判断是否符合条件。
我的代码太难看了放一篇题解:
https://www.luogu.com.cn/blog/ypcaeh/solution-p2218
五、总结
对于一个考二分答案的题,我们首先要找到最大值最小或最小值最大这种字眼,然后再想可不可以用二分答案。首先把题目反过来,想如何判断答案是否符合条件,然后写出判断函数,最后把模板敲上。
二分答案也经常与其他的算法配合,其实就是对这些算法的优化。当你发现答案有单调性时,就可以考虑是否可以用二分答案。
完结撒花~