最近做了不少二分的题,发现二分的运用真的很活,有的时候真的很难发现这是二分。下面是我对于二分的一些典型的题目和进阶题目的总结。
一、二分的模版
分别是我常用的整数二分模版和浮点数二分模版
int erfen(){
int high;
int low,mid;//在这里high和low的具体赋值要根据题目具体要求而定
if(high-low>1){
mid=(high+low)/2;
if(ok(mid)){// ok(mid) 是自己写的函数,来判断向上二分还是向下二分,一般根据题目具体定。
high=mid;
}
else low=mid;
}
printf("%d",low或者high)//到底输出哪一个要根据具体题目来看
}
int erfen(){
double high,low,mid;
int i;
for(i=0;i<=100;i++){
if(ok(mid)){
high=mid;
}
else low=mid;
}
printf("%lf",low或者high)
}
二、下面是关于二分的简单应用 我们来看两道经典题
http://poj.org/problem?id=1064
题目大意:给出N条绳子,长度分别为L。若从他们中切割出k条相同的绳子的话,这K条绳子最长多长。
分析:我们知道要求的答案最后一定是一个确定的结果,并且这个结果的范围可以确定出来(0~INF),那么这个时候我们就要想到二分,通过二分这个结果的范围根据限制条件来直接查找结果。
#include<cstdio>
#include<cstring>
#include<cmath>
#define maxn 10010
#define INF 100001
double a[maxn];
double left,right,mid;
int n,k;
bool ok(double x)
{
int num=0,i;
for(i=0;i<n;++i) //判断当长度是mid时是否存在k条这样的绳子
num+=(int)(a[i]/x);
return num>=k;
}
int main()
{
while(scanf("%d%d",&n,&k)!=EOF)
{
int i;
for(i=0;i<n;++i)
scanf("%lf",&a[i]);
left=0;right=INF;
for(i=0;i<=100;i++)
{
mid=(left+right)/2;
if(ok(mid))
left=mid;
else
right=mid;
}
printf("%0.2f\n",floor(right*100)/100);//floor表示对浮点数向下取整
}
return 0;
}
http://poj.org/problem?id=2456//最大化最小值
该题目让最大化两头牛之间的最小距离
这个题目也是上面那个思路 ,我们能够确定结果的范围为(0,INF)或者把范围优化成(两牛棚间的最小间距,牛棚总的距离),那么接下来我们只要找到限制二分条件即可。详细解释见代码
#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<string.h>
using namespace std;
const int MAX = 100010;
int a[MAX],n,m;
bool ok(int d)
{
int t = a[0],count = 1;
for(int i = 1;i < n;i ++)
{
if(a[i] - t >= d)
{
count ++;
t=a[i];
if(count >= m)//判断是否满足至少有m个牛棚之间的距离大于d
return true;
}
}
return false;
}
int solve()
{
int i;
int low = 0,high = a[n-1] - a[0];
for(i=1;i<=100;i++)
{
int mid=(low+high)/2;
if(ok(mid))
low=mid;
else
high=mid;
}
return low ;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 0;i < n;i ++)
scanf("%d",&a[i]);
sort(a,a+n);
printf("%d\n",solve());
return 0;
}
三、二分的进阶
通过上面两个题我们注意到,一般做二分题的套路就是对答案进行二分,首先找到答案的范围是什么,再者 找的是限定条件,有的时候很难看出来这个题考的是二分比如下面这个例题
http://codeforces.com/problemset/problem/671/B
题目大意:Robin Hood每天将该镇最富裕的那个人的钱币给最穷的那个人,这样进行k天,当最穷的人和最富裕的人有相同的钱币时,Robin Hood将不再交换钱币。问第k天,该镇中最穷的人和最富的人金币差多少。
分析:稍微分析一下过程,我们发现这个结果算的是一个差值,而且这个差值是根据每一天在进行变化的,我们无法通过第k天的结果限制得到这个差值的限制条件。 因此我们无法直接对差值二分,我们需要通过二分其他变量来最终得到这个差值。由于 差值=富人-穷人,那么我们很自然的想到可以通过 二分得到第k天的最大值和最小值 最终得到差值,那么这个限制条件怎么确定呢。在这里我们不要去管中间的过程到底怎么样,我们只需要知道 若第k天的最大值是max 那么 对于第一天所给的镇上人财富的数据,比max大的人的钱币一定会被分走,而富人们要被分走的钱币数量的总和就是如果最大值为max 所需要的天数(因为分走一个钱币需要花费一天) ,同理可找到 最小值min和天数的关系,而这两个关系就是 二分的限定条件
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
ll b[500005];
int n,k;//k次操作
int ok(int mx) {
ll kk = 0,tt = 0;
for(int i = 0; i < n; i++) {
if(b[i] > mx) {//统计比当前答案大得可以拿出多少
kk += (b[i]-mx);
} else {//统计比当前答案小的一共得到多少
tt += (mx-b[i]);
}
}
if(kk <= k && tt >= kk) return 1;//多的数必须小于天数,并且小的必须大于多的,说明答案太大了
return 0;
}
int ok2(int mi) {
ll kk = 0,tt = 0;
for(int i = 0; i < n; i++) {
if(b[i] < mi) {
kk += (mi-b[i]);//比当前答案小的总数
} else {
tt += (b[i]-mi);//比当前答案大的总数
}
}
if(kk <= k && tt >= kk) return 1;//小的总数必须小于天数,大的总数大于小的总数,说明当前的答案太小
return 0;
}
int main() {
scanf("%d%d",&n,&k);
for(int i = 0; i < n; i++) {
scanf("%I64d",&b[i]);
}
sort(b,b+n);
int mx = b[n-1],mi = b[0];
int r = mx,l = mi;
while(l<= r) {
int mid = (l+r)/2;
if(ok(mid)) {
r = mid-1;
mx = mid;
} else {
l = mid+1;
}
}
r = b[n-1];
l = b[0];
while(l<= r) {
int mid = (l+r)/2;
if(ok2(mid)) {
l = mid+1;
mi = mid;
} else {
r = mid-1;
}
}
printf("%d\n",mx-mi);
return 0;
}