1. 程式人生 > >【暑假集訓筆記】二分查詢(二分查詢數值+二分64種類型+最大化最小值+最小化最大值+最大化平均值模型)

【暑假集訓筆記】二分查詢(二分查詢數值+二分64種類型+最大化最小值+最小化最大值+最大化平均值模型)

//二分查詢總結:由於本人二分常年寫成死迴圈
/*
簡介:二分查詢 == 折半查詢
要求:線性表,有序表(注意升序與降序)
思想:
	設查詢區間[L,R]
	取中點 mid = (L+R)/2
	判定mid是否符合要求:(如何判斷:bool check(int mid); )
		是:縮短區間求邊界;直接返回;
		否:縮短區間
	最終結果val = R 或者val = L;
*/

//典型線性表查詢資料
int er_search(int a[],int n, int key)
{
	const int inf = 0x3f3f3f3f;
	
	int L=0,R=n;
	while(L<R){
		int mid = (L+R)/2;
		if(key==a[mid]){
			return mid;
		}
		else if(key<a[mid]){
			R=mid-1;
		}
		else if(k>a[mid]){
			L=mid+1;
		}
	}
	return inf;
} 
//CSDN某部落格對二分的64種分類
/*
*取整方式
	向下取整(最小值) 向上取整(最大值)
	
*區間開閉
	閉區間 左閉右開區間 左開右閉區間 開區間
	
*問題型別
	單增
	對於不下降序列a,求最小的i,使得a[i] = key 
	對於不下降序列a,求最大的i,使得a[i] = key 
	對於不下降序列a,求最小的i,使得a[i] > key 
	對於不下降序列a,求最大的i,使得a[i] < key 
	單減
	對於不上升序列a,求最小的i,使得a[i] = key 
	對於不上升序列a,求最大的i,使得a[i] = key 
	對於不上升序列a,求最小的i,使得a[i] < key 
	對於不上升序列a,求最大的i,使得a[i] > key
*/

//下面四個不下降的例子
//a[] = 1 2 3 4 5 6 6 6 7 9 
//min i,a[i] = key;  =>a[5]
    while(s < e){
        mid = (e+s)/2;// 向下取整
        if(key <= a[mid])
            e = mid;
        else
            s = mid + 1;
    }

//max i,a[i] = key =>a[7]
    while(s < e){
        mid = (e+s+1)/2;// 向上取整
        if(key >= a[mid])
            s = mid;
        else
            e = mid - 1;
    }

//min i, a[i] > key =>=>a[8]
    while(s < e){
        mid = (e+s)/2;//向下取整
        if(key < a[mid])
            e = mid;
        else
            s = mid + 1;
    }

// max i, a[i] < key =>a[4]
    while(s < e){
        mid = (e+s+1)/2;//向上取整
        if(key > a[mid])
            s = mid;
        else
            e = mid - 1;
    }

/*巧記,但不是完全正確
	迴圈:L<R 
	求mid時:求max :L+R+1  求min: L+R;
	if():真實值與猜測值的關係作為條件:max-真實大於猜測 min-真實小於猜測
	防死迴圈:調整if下的L或者R 另一個邊界在else下注意+-1;
	
	總結:迴圈L<R mid注意1 else下防死迴圈
*/
//另一種簡單分類:第一個大於v,第一個大於等於v,最後一個小於v,最後一個小於等於v
/*內容來自:http://www.cnblogs.com/xiaowuga/p/8604750.html
	第一個大於等於v:
		lower_bound(ForwardIterator first, ForwardIterator last,const T& val, Compare comp)
		
		我們假設L為當前區間的答案,R為當前區間的實際答案(因為R是第一個大於等於v的下標),
		我們每次二分的實際上是為了讓L和R不斷靠近,
		所以當L==R的時候,我們假設的答案等於實際的答案,那麼就結束迴圈了,返回答案L。
		while(L<R){
            int M=(L+R)/2;
            if(a[M]>=v) R=M;//R是第一個大於等於v下標,那麼R最大隻能是m
            else L=M+1;//[M,R)區間內的下標都是小於v的,L作為最後的答案最小隻能是M+1
        }
	
	第一個大於v:
		upper_bound(ForwardIterator first, ForwardIterator last,const T& val, Compare comp);
        
		我們假設L為當前區間的答案,R為當前區間的實際答案(因為R是第一個大於v的下標),
		我們每次二分的實際上是為了讓L和R不斷靠近,
		所以當L==R的時候,我們假設的答案等於實際的答案,那麼就結束迴圈了,返回答案L。
		while(L<R){
            int M=(L+R)/2;
            if(a[M]>v) R=M;//R是第一個大於v下標,那麼R最大隻能是M
            else L=M+1;//[M,R)區間內的下標都是小於等於v的,L作為最後的答案最小隻能是M+1
        }
			
	最後一個小於等於v	
	
		我們假設R為當前區間的答案,L為當前區間的實際答案(因為L是最後一個小於等於v的下標),
		我們每次二分的實際上是為了讓L和R不斷靠近,
		所以當L==R的時候,我們假設的答案等於實際的答案,那麼就結束迴圈了,返回答案L。
		while(L<R){
            int M=(L+R+1)/2;
            if(a[M]<=v) L=M;//L是最後一個小於等於v下標,那麼L最小隻能是M。
            else R=M-1;//(L,M]區間內的下標都是大於v的,R作為最後的答案最大隻能是M-1。
        }
			
	最後一個小於v
	
		我們假設R為當前區間的答案,L為當前區間的實際答案(因為L是最後一個小於v的下標),
		我們每次二分的實際上是為了讓L和R不斷靠近,
		所以當L==R的時候,我們假設的答案等於實際的答案,那麼就結束迴圈了,返回答案L。
		while(L<R){
            int M=(L+R+1)/2;
            if(a[M]<v) L=M;//L是最後一個小於v下標,那麼L最小隻能是M。
            else R=M-1;//(L,M]區間內的下標都是大於等於v的,R作為最後的答案最大隻能是M-1。
        }		
*/

//二分條件搜尋的應用:最大化最小值,最小化最大值,最大化平均值
/*最大化最小值
	邊界屬性: 	正確答案ans
				滿足條件但不是最大:ans-1
				不滿足條件:ans+1
				
				二分型別:最後一個滿足條件的ans值
					思路:	把條件看成具體的值
							求最後一個小於等於v
*/
/* 最小化最大值
	邊界屬性: 	正確答案ans
				滿足條件但不是最小:ans+1
				不滿足條件:ans-1		

				二分型別:第一個滿足條件的值
					思路:	把條件看成具體的值
							求第一個大於等於v
*/	

/*最大化平均值:
模型構建:
有n個物品,每個物品分別對應一個重量w和價值v。要求選出k個,使得平均每單位重量的價值最大。

二分模型:搜尋滿足條件check(mid)的最大解
check()模型:
構建過程:
	單個物品:
		平均價值:v/w
		判斷這個物品是否被挑選(是否滿足單位價值mid) v/w>=mid
			化簡得 v-wx>=0; 可記錄c[i] = v-wx(第i個)
	多個物品:
		迴圈所有物品
		
	k個物品
		求和c[0]--c[k-1]
		如果這個和大於零,說明總體的平均值大於mid
	
模型:二分查詢最大化平均值
int n;資料組數
int k;要求件數
bool check(double mid){
	
	c[i] = v[i]-mid*w[i];
	
	sort(c,c+n,cmp);//逆序
	
	double sum=0;
	sum+=c[0] ---c[k-1]
	
	return sum>=0;//sum = 0時候是正好滿足條件
}

*/