【模板】二分模板和例题

二分答案是一种神奇的思想。对于一道题,与通常的求解答案不同,二分答案的思想是“猜出”答案,使用得当会十分巧妙。

它的思想大致与对分查找类似,所以对于求解的题目需要满足一些条件:

  • 答案要有一定的范围
  • 答案要有单调性
  • 要能快速验证猜的答案的正确性

因为二分了复杂度是猜答案范围n的$O(logn)$,对范围的要求很松。

二分可以递归或者循环实现,但递归要有不必要的性能消耗,所以要循环。


P1024 一元三次方程求解
一个是根在[-100,100]内,而且只要精确两位小数,范围是ok的。

三次函数连续,满足单调性。

对于f(x)=0,x1< x2,f(x1)*f(x2)<0则x1,x2间必有一根这个结论可以快速验证答案的正确性。

于是二分答案:

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
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;

double a,b,c,d;
bool vis[20005]={0};
inline double fx(double x){
return a*pow(x,3)+b*pow(x,2)+c*x+d;
}

void solve(double l,double r){
if(fx(l)*fx(r)>0)return;
int t=(r+100)*100;
if(fabs(l-r)<0.001){
if(!vis[t])
printf("%.2lf ",r);
vis[t]=1;
return;
}
double mid=(l+r)/2;
solve(l,mid);
solve(mid,r);

}

int main(){
scanf("%lf%lf%lf%lf",&a,&b,&c,&d);
for(int i=-100;i<100;i++)
solve(i,i+1);
return 0;
}

P1182 数列分段
此题使用了二分答案,就十分巧妙了。

二分的就是要求的答案,最大值的最小值,该答案一定不小于数列中的最大值(一个数一段),不大于该数列的和(只分一段),由题范围在$[0,10^9]$,可以二分求解。

最大值的最小值这个表述,是求满足某一条件后的最值,暗含了答案的单调性。

而此题的重点在如何快速判断猜的答案的正确性。因为数列分段是连续的几项,所以对于要验证的答案,只要以此为基准贪心,尽可能减少划的段数,如果大于要求就是答案太小;相反就是答案太大。
此验证方法是$O(n)$,加上二分总共是$O(nlog\sum{a_i})$,是ok的。

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
#include<iostream>
#include<cstdio>
using namespace std;
const int MAXN=100005;
int n,m;
int a[MAXN]={0};
int ans;

bool judge(int mid){
int sum=0,num=1;
for(int i=1;i<=n;i++){
if(sum+a[i]>mid){
num++;
sum=0;
}
sum+=a[i];
}
if(num>m)return true;
return false;
}

int main(){
scanf("%d%d",&n,&m);
int l=0,r=0,mid;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
l=max(l,a[i]);
r+=a[i];
}

while(l<=r){
mid=(l+r)>>1;
if(judge(mid))l=mid+1;
else r=mid-1;
}

printf("%d",l);
return 0;
}