二分入门和简单进阶

最近做了不少二分的题,发现二分的运用真的很活,有的时候真的很难发现这是二分。下面是我对于二分的一些典型的题目和进阶题目的总结。

一、二分的模版

分别是我常用的整数二分模版和浮点数二分模版

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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值