1. 程式人生 > >二分入門和簡單進階

二分入門和簡單進階

最近做了不少二分的題,發現二分的運用真的很活,有的時候真的很難發現這是二分。下面是我對於二分的一些典型的題目和進階題目的總結。

一、二分的模版

分別是我常用的整數二分模版和浮點數二分模版

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