二分查找与二分答案(错误总结)

二分查找
 
上面是一位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;

 
 

P1873 砍树

砍树,跳石头,木材加工,路标设置都是一类的题目,这类题目主要是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;
}

 
 

P2678 [NOIP2015 提高组] 跳石头

#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;
}

 
 

P2440 木材加工

#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;
}

 
 
P3853 [TJOI2007]路标设置

#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()函数两小时

P1843 奶牛晒衣服

这道题和 三、 里的那个充电设备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;
}

 
 

P3743 kotori的设备

这道题是一个用多重循环搞精度,同时这道题的 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;
}

 
 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值