【暑假集训笔记】二分查找(二分查找数值+二分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时候是正好满足条件
}

*/		

猜你喜欢

转载自blog.csdn.net/F_zmmfs/article/details/81326515