二分查找与二分答案(错误总结)
二分查找
上面是一位daolao队友写的一个总结,然后我开始了我自己的总结
(哟,又开始写bug了?)
一、板子二分
(因为查找和答案分类有点细,所以我按照写法分个类)
二分的板子
(转自上面的超链接,我就是增添了一下输出)
int l=1,r=n;
while(l<r)
{
int mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
cout<<l;
//两个模板都可以用 只不过有的时候下面才能出正确答案
int l=1,r=n;
while(l<=r)
{
int mid=(l+r)>>1;
if(check(mid)) r=mid-1;
else l=mid+1;
}
cout << l-1;
砍树,跳石头,木材加工,路标设置都是一类的题目,这类题目主要是check函数比较难绕出来,其实也不难, 一共也就改个几个小时就能做出来。
数列分段也有类似的地方。
#include<iostream>
using namespace std;
#include<iomanip>
#include<string>
#include<algorithm>
#include<stack>
#include<map>
#include<queue>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<cctype>
#include<cstdio>
typedef unsigned long long ULL;
typedef long long LL;
typedef long L;
const int maxn=1e6+10;
long n,m;
long a[maxn];
int main()
{
scanf("%ld%ld",&n,&m);
for(int i=0;i<n;++i)
{
scanf("%ld",&a[i]);
}
sort(a,a+n);
long left=0,right=1e9,mid,ans;
long cnt;
while(left<=right)
{
mid=(left+right)/2;
cnt=0;
//cout << mid << endl;
for(int i=upper_bound(a,a+n,mid)-a;i<n;++i)
{
cnt+=(a[i]-mid);
}
//cout << cnt << endl;
if(cnt<m)
{
right=mid-1;
}
else if(cnt>=m)
{
ans=mid;
left=mid+1;
}
if(cnt==m)
break;
}
cout << ans << endl;
return 0;
}
#include<iostream>
using namespace std;
#include<iomanip>
#include<string>
#include<algorithm>
#include<stack>
#include<map>
#include<queue>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<cctype>
#include<cstdio>
typedef unsigned long long ULL;
typedef long long LL;
typedef long L;
const int maxn=5e4+10;
LL l,n,m;
LL a[maxn];
int main()
{
scanf("%lld%lld%lld",&l,&n,&m);
for(int i=1;i<=n;++i)
{
scanf("%lld",&a[i]);
}
LL left=0,right=l,mid,cnt,rec;
LL ans;
while(left<=right)
{
mid=(left+right)/2;
rec=0;
cnt=0;
for(int i=1;i<=n;++i)
{
if(a[i]-a[rec]<mid)
++cnt;
else
rec=i;
}
if(cnt<=m)
{
ans=mid;
left=mid+1;
}
else
right=mid-1;
}
cout << ans;
return 0;
}
#include<iostream>
using namespace std;
#include<iomanip>
#include<string>
#include<algorithm>
#include<stack>
#include<map>
#include<queue>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<cctype>
#include<cstdio>
typedef unsigned long long ULL;
typedef long long LL;
typedef long L;
const int maxn=1e5+10;
const int Inf=1e8;
int n;
long k;
long a[maxn];
int main()
{
scanf("%d %ld",&n,&k);
for(int i=0;i<n;++i)
{
scanf("%ld",&a[i]);
}
long left=0,right=Inf,mid;
long cnt;
while(left+1<right)
{
mid=(left+right)/2;
cnt=0;
for(int i=0;i<n;++i)
{
cnt+=(a[i]/mid);
}
if(cnt<k)
{
right=mid;
}
else if(cnt>=k)
{
left=mid;
}
}
cout << left;
return 0;
}
#include<iostream>
using namespace std;
#include<iomanip>
#include<string>
#include<algorithm>
#include<stack>
#include<map>
#include<queue>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<cctype>
#include<cstdio>
typedef unsigned long long ULL;
typedef long long LL;
typedef long L;
const int maxn=1e5+10;
long l,n,k;
long a[maxn];
int flz(long mid)
{
long cnt=0;
for(int i=2;i<=n;++i)
{
if(a[i]-a[i-1]>=mid)
{
cnt+=(a[i]-a[i-1])/mid;
if((a[i]-a[i-1])%mid==0)
--cnt;
}
}
if(cnt>k)
return 0;
else
return 1;
}
int main()
{
scanf("%ld %ld %ld",&l,&n,&k);
for(int i=1;i<=n;++i)
scanf("%ld",&a[i]);
long left=0,right=l,mid;
while(left<right)
{
mid=(left+right)/2;
if( flz(mid)==1 )
{
right=mid;
}
else
{
left=mid+1;
}
}
cout << left;
return 0;
}
P1182 数列分段 Section II
这道题看见了范围我直接开始莽二分,因为范围不超过1e9,直接从0~1e9直接二分找,然后有一个检测点死活过不去。
又考虑到二分方法如果范围不对容易找错,然后开始压缩
l
e
f
t
left
left 和
r
i
g
h
t
right
right 的范围,很容易得出范围。
#include<iostream>
using namespace std;
#include<iomanip>
#include<string>
#include<algorithm>
#include<stack>
#include<map>
#include<queue>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<cctype>
#include<cstdio>
typedef unsigned long long ULL;
typedef long long LL;
typedef long L;
const int maxn=1e5+10;
int n,m;
long a[maxn];
int check(long mid)
{
long cnt=0,total=0;
for(int i=0;i<n;++i)
{
if(total+a[i]<=mid)
total+=a[i];
else
total=a[i],++cnt;
}
if(cnt>=m)
return 1;
else
return 0;
}
int main()
{
scanf("%d%d",&n,&m);
long left=0,right=0,mid;
for(int i=0;i<n;++i)
{
scanf("%ld",&a[i]);
left=max(left,a[i]);
right+=a[i];
}
while(left<right)
{
mid=(left+right)/2;
if(check(mid)==1)
left=mid+1;
else
right=mid;
}
cout << left;
return 0;
}
又是二分板子五分钟,check()函数两小时
这道题和 三、 里的那个充电设备P3743 kotori的设备(超链接就不贴了)有点像,主要是整数不连续。
#include<iostream>
using namespace std;
#include<iomanip>
#include<string>
#include<algorithm>
#include<stack>
#include<map>
#include<queue>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<cctype>
#include<cstdio>
typedef unsigned long long ULL;
typedef long long LL;
typedef long L;
const int maxn=5e5+10;
long n,a,b;
long w[maxn];
int check(long mid)
{
long tsum=0;
for(int i=1;i<=n;++i)
{
if( (w[i]- mid*a)>0 )
{
tsum+=(w[i]-mid*a)/b;
if( (w[i]-mid*a)%b!=0 )
tsum++;
}
}
if( tsum <= mid )
return 1;
else
return 0;
}
int main()
{
scanf("%ld %ld %ld",&n,&a,&b);
for(int i=1;i<=n;++i)
scanf("%ld",&w[i]);
long left=1,right=1e9,mid;
while(left<right)
{
mid=(left+right)/2;
if(check(mid))
right=mid;
else
left=mid+1;
}
cout << left;
return 0;
}
二、lower_bound()和upper_bound()
cpp的STL函数,作用非常的大
P2249 【深基13.例1】查找
这道题比较简单,除了手写二分(当然可能一般人不会去手写)的方法外,调用cpp的库函数lower_bound()函数比较快。
至于判定条件,也很简单。
#include<iostream>
using namespace std;
#include<iomanip>
#include<string>
#include<algorithm>
#include<stack>
#include<map>
#include<queue>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<cctype>
#include<cstdio>
typedef unsigned long long ULL;
typedef long long LL;
typedef long L;
const int maxn=1e6+10;
long a[maxn],b[maxn];
long n,m;
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;++i)
scanf("%ld",&a[i]);
for(int i=0;i<m;++i)
scanf("%ld",&b[i]);
for(int i=0;i<m;++i)
{
int temp=lower_bound(a,a+n,b[i])-a;
if(a[temp]==b[i])
printf("%d ",temp+1);
else
printf("-1 ");
}
return 0;
}
P1678 烦恼的高考志愿
依旧是lower_bound()和upper_bound()函数的用法,不过我踩的坑是a[0]的坑,前一项的差会对结果产生影响,所以需要特判,对没错改了我大概30min 需要注意一下,绝对值是因为满意值的算法。
#include<iostream>
using namespace std;
#include<iomanip>
#include<string>
#include<algorithm>
#include<stack>
#include<map>
#include<queue>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<cctype>
#include<cstdio>
typedef unsigned long long ULL;
typedef long long LL;
typedef long L;
const int maxn=1e5+10;
long m,n;
long a[maxn],b[maxn];
int main()
{
scanf("%ld%ld",&m,&n);
for(int i=0;i<m;++i)
scanf("%ld",&a[i]);
sort(a,a+m);
long alen=unique(a,a+m)-a;
long ans=0;
for(int i=0;i<n;++i)
{
scanf("%ld",&b[i]);
}
for(int i=0;i<n;++i)
{
long pos=lower_bound(a,a+alen,b[i])-a;
if(pos==0)
ans+=abs(a[pos]-b[i]);
else
ans+=min( abs(a[pos]-b[i]) , abs(a[pos-1]-b[i]) );
}
cout << ans;
return 0;
}
三、实数域上的二分
来自书上的板子
确定好所需要的精度 e p s eps eps,以 l + e p s < r l+eps<r l+eps<r为循环条件,每次根据 m i d mid mid 上的判定选择 r = m i d r=mid r=mid或者是 l = m i d l=mid l=mid分支之一即可。一般要保留 位小数时,则取eps=10-(k+2) 。
while(l+1e-5<r)
{
double mid=(l+r)/2;
if(check(mid))
r=mid;
else
l=mid;
}
精度不确定的时候,就干脆使用循环固定次数的二分方法,可以干到一个更高的精度。
for(int i=0;i<100;i++)
{
double mid=(l+r)/2;
if(check(mid))
r=mid;
else
l=mid;
}
P1024 [NOIP2001 提高组] 一元三次方程求解
这道题是个巨经典的实数域上的二分,没啥好说的,控制精度啥的,应该看得懂。
#include<iostream>
using namespace std;
#include<iomanip>
#include<string>
#include<algorithm>
#include<stack>
#include<map>
#include<queue>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<cctype>
#include<cstdio>
typedef unsigned long long ULL;
typedef long long LL;
typedef long L;
double a,b,c,d;
double f(double x)
{
return (a*x*x*x+b*x*x+c*x+d);
}
int main()
{
scanf("%lf %lf %lf %lf",&a,&b,&c,&d);
int cnt=0;
for(double i=-100;i<=100;i+=0.001)
{
double l=i,r=i+0.001;
double mid=(l+r)/2;
if( f(l)*f(r)<=0 )
{
double mid=(l+r)/2;
printf("%.2lf ",mid);
cnt++;
}
if(cnt==3)
break;
}
return 0;
}
这道题是一个用多重循环搞精度,同时这道题的 r i g h t right right卡了数据,我也不知道为啥,3e9就过了,1e9卡了一个测试点。然后对于 c h e c k ( ) check() check()函数的写法,对于 t s u m tsum tsum的类型选错了。
这道题和上面有一道P1843 奶牛晒衣服特别像,我放在上面,基本就是 c h e c k ( ) check() check()函数写好就对了,但是这道题更多是牵扯到实数域的连续性,而奶牛那道题更多的是整数数列的处理。
#include<iostream>
using namespace std;
#include<iomanip>
#include<string>
#include<algorithm>
#include<stack>
#include<map>
#include<queue>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<cctype>
#include<cstdio>
typedef unsigned long long ULL;
typedef long long LL;
typedef long L;
const int maxn=1e5+10;
long n,p;
long a[maxn],b[maxn];
long zsum=0;
int check(double mid)
{
double timesum=0;
for(int i=1;i<=n;++i)
{
if(b[i]-(mid*a[i])<0)
timesum+=( b[i]-(mid*a[i]) );
}
timesum=fabs(timesum);
if( timesum>=p*mid )
return 1;
else
return 0;
}
int main()
{
scanf("%ld%ld",&n,&p);
double left=1e9+10,right=-1,mid;
for(int i=1;i<=n;++i)
{
scanf("%ld %ld",&a[i],&b[i]);
zsum+=a[i];
left=min(left,b[i]*1.0/a[i]);
//right=max(right,b[i]*1.0/a[i]);
}
if(p>=zsum)
{
printf("-1");
return 0;
}
right=3e9+10;
for(int i=0;i<100;++i)
{
mid=(left+right)/2;
//cout << left << " " << right << endl;
if(check(mid))
right=mid;
else
left=mid;
// if(left==right)
// break;
}
cout << left << endl;
return 0;
}