二分的用处太大了,不管是求简单的方程,还是求最优解方面都是不错的解题思想。
只要在线性,顺序或者有序的数据里就可以用二分来找最优的答案,而且时间平均都是O(log2 n)。题目中好像是HDU 4190吧,这题的限时是10000ms,而用二分做才用时1000ms,其优点可想而知。
不过就像《编程珠玑》中说的一样,虽然二分思路及其做法很爽,但是编写二分的程序总是错漏百出的。二分的第一个程序出现在1942年,但是直到1962年出出现了第一个没有bug的二分程序,其编写正确难度可想而知。
解题技巧:
(1)整形数据题目:l 为下界,r 为上界
一般的整形数据的题其循环都是 :
while ( l < r ) 然后l=mid+1,high=mid 这各形式的;
或者有的题目边界要求比较强就得是
while ( l <= r) 然后 l=mid+1,r=mid-1 这各形式;
还有道题就是CF 371C那道中的边界处理要求比较高就是:
while( l+1 < r ) 然后 l=mid,r=mid
(2)浮点型题目: #define eps 1e-5
大神说的话:“一般浮点型题目都会与精度打交道,所以势必与eps有关,因为如果如果精度要求0.01,那么如果你在 l=mid+eps这样做的话,这里我设eps为0.00001,那么时间复杂度就会乘以10^3了,那么既然二分是减少时间的,这样又会增加时间复杂 度,那该怎么避免这个problem呢。
所以在HDU 1551这题上我就掉进了这个坑了,我把精度写在 l=mid+eps里了,然后直接TLE。 我把精度写在while里面的时候时间直接下降很多。因为每次都是平分,这就与eps没多大关系了,只要能接近最优答案就行。所以技巧如下:
while( r - l >eps) 然后 l=mid , r=mid;即可。”。
解题的基本思路就是:
while( l < r )
{
mid=(l+r)/2; //如果是整数用移位>>1更加快
if(gao(mid)<=m) l=mid+1; //gao函数是处理二分枚举之后验证最佳答案是否符合的函数
else r=mid;
}
下面是这些题的代码及分析:
HDU1551 Cable master http://acm.hdu.edu.cn/showproblem.php?pid=1551
算是我的二分第一题吧,题意是给你n根绳子,让你分成长度相等m段,求绳子的最长长度。以前根本不知道这种题可以用二分来做,感觉很神奇。
代码如下:
#include <iostream> #include <cstdio> #include <cstring> #include <math.h> #include <algorithm> #define eps 1e-8 using namespace std; int n,m; double sum; double a[10001]; bool judge(double s) { int cnt=0; for(int i=0; i<n; i++) { cnt+=(int)(a[i]/s);//这根绳子可以分得段数 } if(cnt>=m) return true; else return false; } int main() { while(scanf("%d%d",&n,&m)!=EOF&&(n||m)) { sum=0; for(int i=0; i<n; i++) { scanf("%lf",&a[i]); sum+=a[i]; } sum/=m;//绳子可以分的最大长度 double l=0,r=sum; while(fabs(r-l)>eps) { double mid=(l+r)/2.0; if(judge(mid)) l=mid; else r=mid; } printf("%.2lf ",l); } return 0; }
HDU4109: Distributing Ballot Boxes http://acm.hdu.edu.cn/showproblem.php?pid=4190
这题就是上面那题的变形。
有N个城市,M个投票箱。
然后是N行,表示每个城市的人口数。
现在每个城市所有的人要投票,投票箱的大小可以无限大(投票箱全部相同,大小相等),我们现在要求的是最小的投票箱容纳量。
解题思路:
如果N == M,则容量肯定为城市人口数最多的那个。
如果N < M,我们当然要把M全部用光,因为用的箱子越多,平均值越小。这样,我们就可以二分枚举人口数作为投票箱的容量,如果对于当前容量,我们枚举所有城市需要 的箱子数目,小于需要的箱子就说明还可以再小,如果需要的箱子数大于给出的箱子数,说明结果不符。
PS:
#include <iostream> #include <cstdio> #include <cstring> #include <math.h> #include <algorithm> #define eps 1e-9//1e-5就WA了 using namespace std; int n,m; double maxx,mid,sum; double a[500001]; int judge(int r) { int cnt=0; for(int i=0; i<n; i++) { if(a[i]<=r) cnt++; else { cnt+=(int)(a[i]/r); if(a[i]/r-(int)(a[i]/r)>eps) cnt++; } } if(cnt>m) { return 0; } return 1; } int main() { while(scanf("%d%d",&n,&m)!=EOF&&(n+m)!=-2) { sum=0; maxx=0; for(int i=0; i<n; i++) { scanf("%lf",&a[i]); maxx=max(maxx,a[i]); sum+=a[i]; } if(n==m) { printf("%.0lf ",maxx); continue; } sum/=m; double l=sum,r=maxx; while(fabs(r-l)>eps) { mid=(l+r)/2; if(judge(mid)==1) { r=mid; } else { l=mid; } } printf("%.0lf ",l); } return 0; }
大神的代码,写的比我的好
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<cmath> #include<climits> using namespace std; int a[500010]; int main() { int city, box, res, r, l, mid; int sum; bool flag; while(scanf("%d%d", &city, &box)) { if(city == - 1 && box == -1) break; res = 0; for(int i = 0; i < city; ++i) { scanf("%d", &a[i]); res = max(res, a[i]); } if(city == box) { printf("%d ", res); continue; } l = 1, r = res; while(l < r) { flag = true; sum = 0; mid = (l + r) >> 1; for(int i = 0; i < city; ++i) { sum += ceil(a[i] * 1.0 / mid); //测试中间值 if(sum > box) flag = false; } if(flag) r = mid; //中间值可以就减半 else l = mid + 1; //不可以则mid+1 } printf("%d ", r); } return 0; }
POJ3273: Monthly Expense http://poj.org/problem?id=3273
题意:就是给出n个数,然后分成m组,要求分得各组的花费之和应该尽可能地小,最后输出各组花费之和中的最大值。
感觉:二分可以求方程的解,这个我承认。不过这题确实有点神哦……这样也能二分,太神了.
题意:二分枚举最佳的最大值,然后用最大值去枚举这n个数能分成的组数,逐渐逼近最优答案即可。
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #define inf 0x3f3f3f3f using namespace std; int n,m,maxx,a[100005]; int sum; bool judge(int r) { int cnt=1; int count=0; for(int i=0; i<n; i++) { if(count+a[i]<=r) count+=a[i]; else { cnt++; count=a[i]; } } if(cnt<=m) return true; return false; } int main() { while(scanf("%d%d",&n,&m)!=EOF) { sum=0; maxx=-1; for(int i=0; i<n; i++) { scanf("%d",&a[i]); maxx=max(maxx,a[i]); sum+=a[i]; } int l=maxx,r=sum; while(l<r) { int mid=(l+r)/2; if(judge(mid)) { r=mid; } else l=mid+1; } cout<<l<<endl; } return 0; }
sdut2862:勾股定理(二分基础题) http://acm.sdut.edu.cn/sdutoj/problem.php?action=showproblem&problemid=2862
校赛的题目,当时没做出来,以后只要遇到查找的题目首先就要考虑二分查找,因为普通查找时间复杂度O(n),而二分查找是O(lgn)。
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> using namespace std; int n; long long a[1001]; bool er(long long a[],int lf,int rf,long long key) { int l=lf,mid; int r=rf; while(l<=r) { mid = l+(r-l)/2; if(a[mid]==key) return true; else if(a[mid]>key) { r=mid-1; } else { l=mid+1; } } return false; } int main() { int T,sum; scanf("%d",&T); while(T--) { sum=0; scanf("%d",&n); for(int i=0; i<n; i++) { scanf("%lld",&a[i]); a[i]=a[i]*a[i]; } sort(a,a+n); for(int i=0; i<=n-3; i++) { for(int j=i+1; j<=n-2; j++) { bool t=er(a,j+1,n-1,(a[i]+a[j])); if(t) { sum++; } } } cout<<sum<<endl; } return 0; }