本次训练共10题,本文附AC代码和题目链接。

nefu 1648 小清新切绳子【找最小值的最大值】

二分查找最大值的最小值为ans,judge函数中return的时候大于和等于是同一种情况
等于号在judge(m)中,则满足if(judge(m))时记下ans=m。

#include <bits/stdc++.h>
using namespace std;
int l,r,n,m,k,ans,a[10010];
bool judge(int m)
{
    int s=0;
    for(int i=1;i<=n;i++)
        s=s+a[i]/m;
    return s>=k;//有等于号
}
int main()
{
    ios::sync_with_stdio(false);
    while(cin>>n>>k)//分成k段
    {
        for(int i=1;i<=n;i++)
            cin>>a[i];
        l=0,r=10000000;
        while(l<=r)
        {
            m=l+(r-l)/2;
            if(judge(m))ans=m,l=m+1;//满足if(judge(m)),记下ans
            else r=m-1;
        }
        printf("%d\n",ans);
    }
    return 0;
}

洛谷 P1577 切绳子(实数版)

思路:实数二分,化实数为整数。

实数二分,精度会缺失(如果直接用double二分,最后%.2lf输出的时候会自动向上取整,可能改变了答案),解决办法是先把每根绳子长度a[i]乘以100化为整数,再按整数的方法二分,最后输出答案时再除以100即可。

注意在二分过程中要特判m=0的情况(否则在judge函数中会除以0导致RE),m=0时直接break退出二分循环,记下答案ans=0。

AC代码:

#include <bits/stdc++.h>
using namespace std;
const int N=1e4+10;
double x,ans;
int n,k,l,r,m,a[N];
bool judge(int m)
{
    int s=0;
    for(int i=1;i<=n;i++)
        s=s+a[i]/m;//m!=0才可使用judge函数判断
    return s>=k;//有等于号
}
int main()
{
    ios::sync_with_stdio(false);
    while(cin>>n>>k)//分成k段
    {
        for(int i=1;i<=n;i++)
        {
            cin>>x;
            a[i]=(int)(x*100);
        }
        l=0,r=1e7;
        while(l<=r)
        {
            m=l+(r-l)/2;
            if(m==0){ans=0;break;}
            //n=1 k=10000 a[1]=1.00时,二分过程中会出现m=0的情况,在judge函数中会除以0导致RE
            //要防止RE,则特判二分过程中m=0直接break,退出二分,记下答案ans=0
            if(judge(m))ans=1.0*m,l=m+1;//满足if(judge(m)),记下ans
            else r=m-1;
        }
        printf("%.2lf\n",ans/100.0);
    }
    return 0;
}

nefu 1211 卖古董-DP-二分【找最大值的最小值】

二分查找最大值的最小值为ans,judge函数中return的时候小于和等于是同一种情况
等于号在judge(m)中,则满足if(judge(m))时记下ans=m。

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,k,t,l,r,ans,a[N];
bool judge(int mid)
{
    int s=0,num=1;
    for(int i=1;i<=n;i++)
    {
        if(s+a[i]<=mid)s+=a[i];
        else s=a[i],num++;
    }
    return num<=k;
}
int main()
{
    ios::sync_with_stdio(false);
    cin>>t;
    while(t--)
    {
        cin>>n>>k;
        l=0;r=0;
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
            r+=a[i];
            l=max(l,a[i]);
        }//这里必须要缩小初始区间,如果写l=0,r=0x3f3f3f3f是错的
        while(l<=r)
        {
            int m=l+(r-l)/2;
            if(judge(m))ans=m,r=m-1;//满足if(judge(m)),记下ans
            else l=m+1;
        }
        printf("%d\n",ans);
    }
    return 0;
}

洛谷 P1182 数列分段 Section II

其实这题和卖古董是同一题,思路是一样的。

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,k,l,r,ans,a[N];
bool judge(int mid)
{
    int s=0,num=1;
    for(int i=1;i<=n;i++)
    {
        if(s+a[i]<=mid)s+=a[i];
        else s=a[i],num++;
    }
    return num<=k;
}
int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>k;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        r+=a[i];
        l=max(l,a[i]);
    }
    while(l<=r)
    {
        int m=l+(r-l)/2;
        if(judge(m))ans=m,r=m-1;
        else l=m+1;
    }
    printf("%d\n",ans);
    return 0;
}

nefu 1647 小清新的二倍问题加强版

方法1,O(n*logn),二分算法,1505ms

先排序,再二分查找。

#include <bits/stdc++.h>
using namespace std;
int n,i,l,r,m,cnt,ans,a[10010];
bool seek(int l,int r,int cmp)
{
    while(l<=r)
    {
        m=l+(r-l)/2;
        if(a[m]>cmp)r=m-1;
        if(a[m]<cmp)l=m+1;
        if(a[m]==cmp)return 1;
    }
    return 0;
}
int main()
{
    ios::sync_with_stdio(false);
    cin>>n;
    while(n--)
    {
        ans=cnt=0;
        while(cin>>a[cnt]&&a[cnt])cnt++;
        sort(a,a+cnt);
        for(i=0;i<cnt;i++)
        ans=ans+seek(0,cnt-1,2*a[i]);
        printf("%d\n",ans);
    }
    return 0;
}

方法2,O(n),桶排思想(其实就是打标记),539ms

输入的时候直接标记每个数的2倍,第二次遍历的时候如果被标记过则答案+1。

这里有个玄学的地方,不能把vis标记数组写成map形式,否则就超时了。

#include <bits/stdc++.h>
using namespace std;
const int N=1e4+10,M=2e5+10;
int t,x,n,ans,a[N],vis[M];
//不能写成map<int,int>vis; 否则超时
int main()
{
    ios::sync_with_stdio(false);
    cin>>t;
    while(t--)
    {
        n=0;
        memset(vis,0,sizeof(vis));
        while(cin>>x&&x)
            vis[2*x]=1,a[++n]=x;
        ans=0;
        for(int i=1;i<=n;i++)
            if(vis[a[i]])ans++;
        printf("%d\n",ans);
    }
    return 0;
}

nefu 1646 小清新的二分查找之旅

二分查找模板题。

#include <bits/stdc++.h>
using namespace std;
int n,q,k,i,l,r,m,a[1000010];
bool judge(int l,int r,int cmp)
{
    while(l<=r)
    {
        m=l+(r-l)/2;
        if(a[m]>cmp) r=m-1;
        if(a[m]<cmp) l=m+1;
        if(a[m]==cmp) return 1;
    }
    return 0;
}
int main()
{
    ios::sync_with_stdio(false);
    while(cin>>n>>q)
    {
        for(i=1;i<=n;i++)
        cin>>a[i];
        while(q--)
        {
            cin>>k;
            if(judge(1,n,k))printf("no\n");
            else printf("YES\n");
        }
    }
    return 0;
}

nefu 1645 小清新的函数坐标

#include <bits/stdc++.h>
using namespace std;
double m,y;
double f(double x)
{return 0.0001*x*x*x*x*x+0.003*x*x*x+0.5*x-3;}
double seek(double l,double r)
{
    while(r-l>1e-5)
    {
        m=l+(r-l)/2;
        if(f(m)>y) r=m;
        if(f(m)<y) l=m;
        if(fabs(f(m)-y)<1e-5) return m;
        //注意两个double类型不能直接判断是否相等,可以在差值的绝对值小于1e-5的情况下认为它们相等
    }
    return m;
}
int main()
{
    ios::sync_with_stdio(false);
    while(cin>>y)
    printf("%.4lf\n",seek(-20,20));
    return 0;
}

nefu 956 二分查找

可以自己写二分查找函数,也可以用STL中的upper_bound函数。

//296ms
#include <bits/stdc++.h>
using namespace std;
int n,x,i,l,r,m,a[1000010];
int seek(int l,int r,int cmp)
{
    while(l<=r)
    {
        m=l+(r-l)/2;
        if(a[m]>cmp)r=m-1;
        if(a[m]==cmp)return m+1;
        if(a[m]<cmp)l=m+1;
    }
    return r+1;
}
int main()
{
    ios::sync_with_stdio(false);
    while(cin>>n>>x)
    {
        for(i=0;i<n;i++)
            cin>>a[i];
        sort(a,a+n);
        printf("%d\n",seek(0,n-1,x));
    }
    return 0;
}

本题也可以用C++现成的函数!
C++其实有直接求出答案的函数:upper_bound,直接用这个函数比自己写的二分速度还快一些
upper_bound作用是返回数组a中第一个大于x的元素的下标ans,用法是ans=upper_bound(a,a+n,x)-a;

//136ms
#include <bits/stdc++.h>
using namespace std;
int n,x,i,a[1000010];
int main()
{
    ios::sync_with_stdio(false);
    while(cin>>n>>x)
    {
        for(i=0;i<n;i++)
            cin>>a[i];
        printf("%d\n",upper_bound(a,a+n,x)-a);
    }
    return 0;
}

nefu 1303 简单几何-二分

这题要特别注意精度:
1、π用acos(-1.0)表示
2、写原始式v2-v1,如果化简v2-v1= h * ( p * r * r - r )= h * r * ( p * r * - 1 ),精度会错
3、实际上还要保留到8位小数而不是4位小数(题目的bug)

#include <bits/stdc++.h>
using namespace std;
const double p=acos(-1.0);//定义π
int t,h;
double seek(double l,double r)//二分查找半径,以下用m表示半径了
{
    double m,ans;
    while(r-l>=1e-8)
    {
        m=l+(r-l)/2.0;
        double v1=m*h;
        double v2=p*m*m*h;
        if(v2-v1<=pow(m,p))ans=m,r=m;//减小m(减小半径)
        //由于精度要求,if里面必须写原始式v2-v1
        else l=m;//增大m(增大半径)
    }
    return ans;//题目要求输出保留4位小数,但是这里必须要写到8位小数的精度(题目的bug)
}
int main()
{
    ios::sync_with_stdio(false);
    cin>>t;
    while(t--)
    {
        cin>>h;
        printf("%.4lf\n",seek(0,1e5));
    }
    return 0;
}