二分法,是通過不斷縮小解的可能存在的範圍,從而求得問題的最優解的方法。經常有二分與其他演算法結合的題目。
1.從有序陣列查詢某個值 -- 以stl中的lower_bound與upper_bound為例
lower_boud( begin, end, val ) 函式輸入需要查詢的有序數列前閉後開區間,查詢數列中第乙個》=val的位置。而upper_bound返回數列中第乙個》val的位置。
如果不存在符合條件的值則返回end(可能會越界)。如果數列中存在val,則[ lower_boud , upper_bound)之間的位置均為val。
lower_boud與upper_bound的實現(與stl中稍有不同):
int lower_bound( int *a, int length, intval )
return
ub;}
int upper_bound( int *a, int length, int
val )
return
ub;}
可以這樣理解:對於lower_bound,如果此時的 mid 滿足 a[ mid ] >= val ,那麼就將右端點變為mid,讓區間向左端靠近,最後的mid為最左端且滿足》=val的位置。
同理upper_bound:讓區間向滿足》val的位置靠近,一步步縮小區間長度。
2.判斷乙個解是否可行
cable master (poj no.1064 ) 參見《挑戰程式設計競賽》p140
有n條繩子,它們的長度分別是li。如果我們從它們中切割出k條長度相同的繩子的話,每條繩子最長有多長?答案保留到小數點兩位數。
與上面搜尋的思路類似,我們用c(x):每條繩子為x滿足切割出k條的條件。每次判斷條件是否成立,如果成立則用二分的方式將區間向右端移動(lb = mid)。
那麼現在的問題是如何高效實現c(x):對於長度為li的繩子最多能切割floor(li / x)個繩子,那麼所有能切割繩子個數的總和》=k即滿足條件。
#include#includeconst
int max_n = 10000
;const
int inf =1e8;
intn,k;
double
l[max_n];
bool c( double
x );
void
solve();
intmain()
bool c( double
x )
return cnt>=k;
}void
solve()
printf(
"%.2lf\n
",floor(lb*100)/100
); //保留二位小數
}
二分搜尋法結束的判定:在輸出小數問題中,一般會指定允許的誤差範圍或是指定輸出中小數點後面的位數。因此在使用二分搜尋法時,有必要設定合理的結束條件
來滿足精度的要求。在上面的程式中我們指定了程式的迴圈次數作為終止條件。1次迴圈可以把區間縮小一半,100次迴圈則可以達到1e-30的精度範圍,基本上是沒有
問題的。此外還可以把中製條件設為( ub - lb ) > eps,指定區間的大小。在這種條件下,如果eps取得太小,就有可能會因為浮點小數精度問題的原因陷入死迴圈,請千萬小心。
3.最大化最小值
憤怒的牛
農夫 john 建造了一座很長的畜欄,它包括n(2≤n≤100,000)個隔間,這些小隔間依次編號為x1,...,xn(0≤xi≤1,000,000,000). 但是,john的c(2≤c≤n)頭牛們並不喜歡這種布局,
而且幾頭牛放在乙個隔間裡,他們就要發生爭鬥。為了不讓牛互相傷害。john決定自己給牛分配隔間,使任意兩頭牛之間的最小距離盡可能的大,那麼,這個最大的最小距離是什麼呢?
類似最大化最小值/最小化最大值的問題,通常可以用二分法就可以很好的解決。
我們令c( d ):可以安排牛的位置使得任意兩頭牛的位置都不小於d。
c(d)的判斷可以用貪心法很好的解決:
對牛舍的位置排序
把第一頭牛放入x0的牛舍
如果第 i 頭牛放入 xi ,則第 i+1 頭牛放入 xk - xj >= d 的最小的k中
之後思路與之前類似:二分判斷,如果 d 符合條件則讓區間向右端移動(l = mid),否則向左移動。
#include#includeusing
namespace
std;
const
int inf =1e8;
const
int max_n = 100000
;int n,m; //
n個隔間 m頭牛
intx[max_n];
bool c( int
d );
void
solve();
intmain()
bool c( int
d )
if( crt==n )
last = crt; //
更新last
}
return
true;}
void
solve()
printf(
"%d\n
",lb);
}
數列分段ii
對於給定的乙個長度為n的正整數數列a[i],現要將其分成m(m≤n)段,並要求每段連續,且每段和的最大值最小。
關於最大值最小:
例如一數列4 2 4 5 1要分成3段
將其如下分段:
[4 2][4 5][1]
第一段和為6,第2段和為9,第3段和為1,和最大值為9。
將其如下分段:
[4][2 4][5 1]
第一段和為4,第2段和為6,第3段和為6,和最大值為6。
並且無論如何分段,最大值不會小於6。
所以可以得到要將數列4 2 4 5 1要分成3段,每段和的最大值最小為6。
最大化最小值,同樣的二分思路,唯一不同的是c( d )的實現。
令c( d ):任意段的和不大於d。其實現仍然可以用貪心法:
last初值為0
找到使得下標從last-crt和不大於d的最大crt。
last = crt + 1
#includeconstint inf =1e8;
const
int max_n = 100000
;int n,m; //
n個整數 m段
intx[max_n];
bool c( int
d );
void
solve();
intmain()
bool c( int
d )
if( crt==n )
last =crt;
} return
false; //
如果沒用完數字則不符合條件
}void
solve()
printf(
"%d\n
",ub);
}
4.最大化平均值
有n個物品的重量和價值分別是 wi 和 vi 。從中選出 k 個物品使得單位重量的價值最大。
樣例:輸入 n = 3,k = 2,( w,v ) = 輸出0.75(選擇0號和2號物品)
最開始想到的是貪心法求解:選取前 k 個單位重量價值最大的物品。但樣例就是乙個反例:按貪心策略應該選取0號和1號物品,但其單位重量價值並不是最大。
實際上這一題可以用二分法求解:
令c( x ):可以選取 k 個物品使得單位重量價值不小於x。那麼只要求滿足c( x )成立的最大x即可。
問題變為c( x )如何實現:假設我們選取物品的某個集合s,那麼其單位重量價值為∑([i∈s])vi / ∑([i∈s])wi。
c( x ) : ∑( [ i ∈ s ] )vi / ∑( [ i ∈ s ] )wi >= x -->
∑( [ i ∈ s ] )vi >= x * ∑ ( [ i ∈ s ] )wi -->
∑( [ i ∈ s ] )( vi - x*wi ) >=0
因此,我們可以對 vi - x*wi 貪心選取:選擇前 k 個最大的 vi - x * wi,判斷和是否》=0。
#include#includeusing
namespace
std;
const
int inf =1e8;
const
int max_n = 10000
;int
n,k;
intw[max_n], v[max_n];
double res[max_n]; //
儲存 vi - x*wi
bool c( double
x );
void
solve();
intmain()
solve();
return0;
}bool c( double
x )
sort(res,res+n);
/*計算res最大的k個數的和
*/double sum = 0
;
for( int i=0; i)
return sum>=0;}
void
solve()
printf(
"%.2lf\n
",lb);
}
二分法應用
eg poj 2456 瘋牛 include include includeusing namespace std typedef long long ll const int maxn 1e7 10 ll a maxn ll n,c 對距離進行二分 int ans 0 int sum 1 int ...
二分法及其拓展
二分查詢及其擴充套件實現 include include include include using namespace std 二分尋找值為value的元素 int binary search vector array,int left,int right,int value left right...
二分法及其變體問題
1.尋找乙個元素在陣列中的位置 二分查詢 int midfind1 int lo,int hi,int tar return 1 2.查詢第乙個等於給定值的元素 查詢第乙個等於給定值的元素 int firsteqpos int lo,int hi,int tar return 1 3.查詢最後乙個等...