一个老问题:
折半查找,第一次出现的位置:
#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.
循环的次数多了,就一定能找到
第一次出现的。
*/
由这个老问题,我们可以发现二分查找这样的性质:
- 如果mid=(l+r)>>1(这样就是mid向下取整),且 l=mid; r=mid;。那 l 永远不可能等于r,只能是与r越来越近。当 l 与r都取整数的时候,如果循环次数够大,那在一定次数之后,l 就只比r小1. 当 l 与r都取double类型的时候,那 l 与r就会逼近,逼近的精度( 10^(-n) )与外层循环的次数有关,循环次数越多,精度越高。
- 如果 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也是这类问题的。