二分入門和簡單進階
最近做了不少二分的題,發現二分的運用真的很活,有的時候真的很難發現這是二分。下面是我對於二分的一些典型的題目和進階題目的總結。
一、二分的模版
分別是我常用的整數二分模版和浮點數二分模版
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;
}