二分的经典应用(折半查找)

 一个老问题:

折半查找,第一次出现的位置:

#include<iostream>
using namespace std;
int main(){
	int n=19,t=4;
	int arr[20]={1,1,1,1,1,
	2,2,2,2,2,3,3,3,3,3,4,4,4,4,4};
	int lp=0,rp=n;
	for(int i=1;i<=20;i++){
		int mid=(lp+rp)/2;
		if(arr[mid]>=t) rp=mid;
		else lp=mid;
	}
	cout<<rp<<endl; //要输出右边的下标,而不是左边的下标。输出哪个,根据实际情况而定。
	return 0;
}
/*
左下标永远不可能和右下标相等,最后是r=l+1. 
循环的次数多了,就一定能找到
第一次出现的。 
*/

由这个老问题,我们可以发现二分查找这样的性质:

  1. 如果mid=(l+r)>>1(这样就是mid向下取整),且 l=mid; r=mid;。那 l 永远不可能等于r,只能是与r越来越近。当 l 与r都取整数的时候,如果循环次数够大,那在一定次数之后,l 就只比r小1. 当 l 与r都取double类型的时候,那 l 与r就会逼近,逼近的精度( 10^(-n) )与外层循环的次数有关,循环次数越多,精度越高。
  2. 如果 mid=(l+r+1)>>1(这样就是mid向上取整),且 l=mid;r=mid;那l就有可能等于r。外层循环的执行条件就只能写: L<=R。对这个老问题来说(其他的没仔细研究),这样就不一定能够找到第一次出现的位置。

 下面的所有题,之所以用二分这种方法,就是因为二分每次将问题的规模缩小一半,缩小了问题规模,算法的时间复杂度就低了。

总的来说,就是将普通的枚举用二分来优化。

 (下面的题,基本思路都是枚举,但是用二分来优化)


 假定一个解是否可行:

 Cable Master(Poj):

#include<iostream>
#include<stdlib.h>
#include<cstdio>
#include<cmath>
using namespace std;
const int maxn=10005;
double inf=0;
int n,k;
double L[maxn];
bool Judge(double x){
	int num=0;
	for(int i=1;i<=n;i++) num+=(int) (L[i]/x);
	return num>=k;
}
void Solve(){
	double lb=0,ub=inf;
	for(int i=1;i<=100;i++){
		double mid=(lb+ub)/2;
		if(Judge(mid)) lb=mid;
		else ub=mid;
	}
	printf("%.2f\n",floor(ub*100)/100);
}
int main(){
//	ios::sync_with_stdio(false);
//	cin>>n>>k;
	scanf("%d %d",&n,&k);
	for(int i=1;i<=n;i++){
		//cin>>L[i];
		scanf("%lf",&L[i]);
		inf=max(inf,L[i]);	
	} 
	Solve();
	return 0;
}

最大化最小值: 

 POJ - 2456 Aggressive cows

//这个题的思路就是枚举所有可能的值,但是枚举会超时,
//所以用了折半来优化。 
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>

using namespace std;
int n,c,x[110000];
bool Isable(int d)
{
	int cnt=1,t=x[1];
	for(int i=2;i<=n;i++)
		if(x[i]-t>=d)
		{
			t=x[i];cnt++;
			if(cnt>=c)return true;
		}
	return false;
}
int main()
{
	scanf("%d%d",&n,&c);
	for(int i=1;i<=n;i++)
	scanf("%d",&x[i]);
	sort(x+1,x+n+1);
	int l=1,r=x[n]-x[1];
	while(l!=r)
	{
		int m=(l+r+1)>>1;//这里必须要向上取整,否则就会死循环。运算到最后,l永远比r小1. 
		if(Isable(m)) l=m;
		else r=m-1;
	}
	printf("%d",l);
	return 0;
}
/*
解释:
假设有一串数:x1,x2,x3,x4,x5,x6,x7,x8,x9,……,xn
	不向上取整的情况: 
		如果某运算时,r=5,l=3,得到mid=4,且这个mid对应的数组值符合条件,	
		那下一次有l=4,r=5,得到mid=4。
 		所以,l永远不能等于r。就会死循环。 
*/

上面这个题有人用汇编写。。。来自该题讨论区。

从快速排序到二分查找,全是用汇编写的,居然都用了61MS,不知道前面的大牛是怎么过的。。。。。
注意,请用C++编译。


#include <stdio.h>
//By 阿长

int a[100010];
int min,max,dest,n,c,best;

void qsort(int head,int tail){
	_asm{
		mov eax,head
		mov edx,tail
		cmp eax,edx
		JGE over

		push ebx
		mov ecx,eax
		add eax,edx
		shr eax,1
		shl eax,2
		shl ecx,2
		mov edx,a[eax]
		mov ebx,a[ecx]
		mov a[ecx],edx
		mov a[eax],ebx
		mov edx,tail
		shl edx,2
		mov eax,ecx

	start:
		cmp eax,edx
		JGE end
		mov ebx,a[eax]
		
	loop1:				//while (i<j && a[i]<=a[j]) 
		cmp eax,edx
		JGE end
		cmp ebx,a[edx]
		JG endloop1
		sub edx,4
		jmp loop1
	endloop1:

		mov ecx,a[edx]
		mov a[eax],ecx
		mov a[edx],ebx
		add eax,4

	loop2:				//while (i<j && a[i]<=a[j]) 
		cmp eax,edx
		JGE end
		cmp a[eax],ebx
		JG endloop2
		add eax,4
		jmp loop2
	endloop2:

		mov ecx,a[eax]
		mov a[edx],ecx
		mov a[eax],ebx
		sub edx,4
		jmp start

	end:
		pop ebx
		shr eax,2
		push eax

		dec eax
		push eax
		push head
		call qsort
		add esp,8
	
		pop eax

		inc eax
		push tail
		push eax
		call qsort
		add esp,8
	over:

	}

}

main(){

	scanf("%d%d",&n,&c);
	int i;
	for (i=1;i<=n;i++)
		scanf("%d",a+i);
	qsort(1,n);
	min=1;
	max=(a[n]-a[1])/(c-1);
	best=min;
	dest=n*4;
	c--;
	_asm{
			push ebx
			mov eax,min
			mov edx,max

		begin:
			cmp eax,edx
			JG  OK
			add edx,eax
			shr edx,1
			xor ecx,ecx
			mov eax,8
			xor ebx,ebx

		loop3:
			cmp eax,dest
			JG cannot
			
			add ebx,a[eax]
			sub ebx,a[eax-4]
			add eax,4
			cmp ebx,edx
			JL loop3
			xor ebx,ebx
			inc ecx
			cmp ecx,c
			JGE can
			jmp loop3

		cannot:
			dec edx
			mov max,edx
			mov eax,min
			jmp begin

		can:
			cmp best,edx
			JGE notupdate
			mov best,edx
			
		notupdate:
			inc edx
			mov min,edx
			mov eax,edx
			mov edx,max
			jmp begin

		OK:
			pop ebx
	}
	printf("%d\n",best);
}

最大化平均值:

//https://ac.nowcoder.com/acm/problem/14662?&headNav=acm下面这个题是牛客的。 
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAX = 1e6+100;
struct hh{
    int c,v;
}a[MAX];
int b[MAX];
int n,k;
bool cmp(int x,int y){
    return x>y;
}
bool check(int x){
    for (int i = 0; i < n;i++){
        b[i]=a[i].v-x*a[i].c;
    }
    sort(b,b+n,cmp);
    ll sum=0;//注意范围:需要用long long
    for (int i = 0; i < k;i++){//求前k个的价值和-重量和*x
        sum+=b[i];
    }
    return sum>=0;
}
int main(){
    int t;
    cin >> t;
    while(t--){
        cin >> n >> k;
        for (int i = 0; i < n;i++){
            cin >> a[i].c >> a[i].v;
        }
        int l=0;
        int r=1e4+10;//注意:二分范围
        int ans=0;
        while(l<=r){//二分
            int mid=(l+r)>>1;
            if(check(mid)){
                l=mid+1;
                ans=mid;
            }
            else r=mid-1;
        }
        cout << ans << endl;
    }
    return 0;
}

//白书上的:https://blog.csdn.net/karry_zzj/article/details/70232097
#include<cstdio>
#include<algorithm> 
using namespace std;
const int maxn = 10000+10;
const int INF = 1000000;
int w[maxn],v[maxn];
double y[maxn];
int n,k;

bool C(double d)
{
    for(int i=0; i<n; i++)
    {
        y[i] = v[i] - d * w[i];
    }
    sort(y, y+n);

    double sum = 0;
    for(int i=0; i<k; i++)
    {
        sum += y[n-i-1];
    }
    return sum >= 0;
}
void solve()
{
    double lb = 0, ub = INF;
    for(int i=0; i<100; i++)//枚举所有可能的单位价值,用折半查找优化。 
    {
    	/*
    	在这种外循环下,必须让lb始终小于ub。 
		所以,mid的求法必须要这样写,让mid向下取整。 
		*/
        double mid = (lb + ub) / 2; 
        if(C(mid)) lb = mid;
        else ub = mid;
    }
    printf("%.2f\n",lb);
}
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=0; i<n; i++)
    {
        scanf("%d%d",&w[i],&v[i]);
    }
    solve();
    return 0;
} 

PS:Poj 3111也是这类问题的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值