• 挑战程序设计竞赛 3.1 不光是查找值!“二分搜索”


    【Summarize】 

      1. 要求最大化 Σai/Σbi 时我们可以考虑二分计算的结果x,那么可以得到 Σai>=Σbi*x,那么我们按照ai-bi*x排序后贪心即可。

      2. 对于两两差值的K值处理,在二分答案之后可以利用尺取法验证。

      3. 求第K值是否满足的情况,可以将小于等于K的置1,大于K的置0,然后进行相关统计。

      4. 在非整数二分时,可以用循环次数来代替eps,一般采用100为循环次数。

      5. 在要求保留答案的非整数二分时,可以在不断的检验过程中直接更新答案,因为那一定是更接近的。

    POJ 3258:River Hopscotch

    /*
        拿掉m块石头,使得每两个石头之间距离的最小值最大。输出这个极值
    */
    #include <cstdio>
    #include <algorithm>
    using namespace std;
    const int N=50010;
    int L,n,m,a[N];
    int check(int x){
        int cnt=0,pre=0;
        for(int i=1;i<=n+1;i++){
            if(a[i]-a[pre]<x)cnt++;
            else pre=i;
            if(cnt>m)return 0;
        }return 1;
    }
    int main(){
        while(~scanf("%d%d%d",&L,&n,&m)){
            for(int i=1;i<=n;i++)scanf("%d",&a[i]); 
            sort(a+1,a+n+1);a[n+1]=L;
            int l=1,r=L,ans; 
            while(l<=r){
                int mid=(l+r)>>1;
                if(check(mid))l=mid+1,ans=mid;
                else r=mid-1;
            }printf("%d
    ",ans);
        }return 0;
    }

    POJ 3273:Monthly Expense

    /*
        将所有的数字按顺序分为m组,使得每组的数字和的最大值最小
    */
    #include <cstdio>
    #include <algorithm>
    using namespace std;
    int n,m,a[100100];
    bool check(int x){
        int s=0,cnt=1;
        for(int i=1;i<=n;i++){
            if(s+a[i]<=x)s+=a[i];
            else{s=a[i];cnt++;if(cnt>m)return 0;}
        }return 1;
    }
    int main(){
        while(~scanf("%d%d",&n,&m)){
            int l=1,r=1000000000,ans;
            for(int i=1;i<=n;i++)scanf("%d",&a[i]),l=max(l,a[i]);
            while(l<=r){
                int mid=(l+r)>>1;
                if(check(mid))r=mid-1,ans=mid;
                else l=mid+1;
            }printf("%d
    ",ans);
        }return 0;
    }

    POJ 3104:Drying

    /*
        题目大意:
            给出n件需要干燥的衣服,烘干机能够每秒干燥k水分,
            不在烘干的衣服本身每秒能干燥1水分
            求出最少需要干燥的时间。
        题解:
            考虑将烘干机的烘干效应变为k-1,那么就是每件衣服在每秒都会自动减少一水分
            如果我们知道最少需要的时间,那么每件衣服自己减少的水分数量就知道了,
            在除去自然减少的水分之后,看看还需要多少k-1的水分减少才能烘干全部的衣服就可以了,
            因此我们二分这个答案,验证是否可行即可。
    */
    #include <cstdio>
    #include <algorithm>
    using namespace std;
    const int N=100010;
    int n,k,a[N];
    bool check(int x){
        int cnt=0;
        for(int i=1;i<=n;i++){
            if(a[i]-x<=0)continue;
            cnt+=(a[i]-x+k-2)/(k-1);
            //printf("%d
    ",cnt);
            if(cnt>x)return 0;
        }return 1;
    }
    int main(){
        while(~scanf("%d",&n)){
            int l=1,r=0,ans;
            for(int i=1;i<=n;i++)scanf("%d",&a[i]),r=max(r,a[i]);
            scanf("%d",&k);
            if(k==1){printf("%d
    ",r);continue;} 
            while(l<=r){
                int mid=(l+r)>>1;
                //printf("%d %d %d
    ",l,r,mid);
                if(check(mid))ans=mid,r=mid-1;
                else l=mid+1;
            }printf("%d
    ",ans);
        }return 0;
    }

    POJ 3045:Cow Acrobats

    /*
        每头牛都有一定的体力和质量,现在将他们叠在一起,
        每头牛的危险值为其上面所有牛的质量减去他的力量值
        现在请最小化最大的危险值
        我们设前n头牛的总质量为s,牛a在牛b前面更优的重要条件为
        s-wa-sa>s-wb-sb。
        所以我们按照w+s排序即可。
    */
    #include <cstdio>
    #include <algorithm>
    #include <climits>
    using namespace std;
    const int N=50010;
    struct data{int w,s;}p[N];
    int n;
    bool cmp(data a,data b){return a.w+a.s<b.w+b.s;}
    int main(){
        while(~scanf("%d",&n)){
            int ans=INT_MIN,sum=0;
            for(int i=1;i<=n;i++)scanf("%d%d",&p[i].w,&p[i].s);
            sort(p+1,p+n+1,cmp); 
            for(int i=1;i<=n;i++){
                ans=max(ans,sum-p[i].s);
                sum+=p[i].w;
            }printf("%d
    ",ans);
        }return 0;
    }

    POJ 2976:Dropping tests

    /*
        题目大意:
            给出每门成绩的总分和得分,去除k门成绩之后
            使得剩余的成绩分数和除以总分得到的数字最大,要求精度在三位小数之内四舍五入到整数
        题解:
            如果答案是x,那么必有选取的几门课程sigma(a*100)>=sigma(b*x)
            至于选取,就可以根据a*100-b*x排序,贪心选取即可。
            对于最后的精度处理问题,只要将数据放大处理末尾即可。
    */
    #include <cstdio>
    #include <algorithm>
    using namespace std;
    const int N=1010;
    struct data{long long a,b;}p[N];
    int n,m,k; 
    bool cmp(data a,data b){return a.a*1000000-a.b*m>b.a*1000000-b.b*m;}
    bool check(int x){
        m=x;
        sort(p+1,p+n+1,cmp);
        long long cnt=0;
        for(int i=1;i<=k;i++)cnt+=p[i].a*1000000-p[i].b*x;
        return cnt>=0;
    }
    int main(){
        while(~scanf("%d%d",&n,&k),n+k){
            k=n-k;
            for(int i=1;i<=n;i++)scanf("%lld",&p[i].a);
            for(int i=1;i<=n;i++)scanf("%lld",&p[i].b);
            int l=0,r=1000000,ans;
            while(l<=r){
                int mid=(l+r)>>1;
                if(check(mid))l=mid+1,ans=mid;
                else r=mid-1;
            }printf("%d
    ",ans/10000+(ans%10000>=5000));
        }return 0;
    }

    POJ 3111:K Best

    /*
        题目大意:
            选取k个物品,最大化sum(ai)/sum(bi)
        题解:
            如果答案是x,那么有sigma(a)>=sigma(b*x)
            至于选取,就可以根据a-b*x排序,贪心选取即可。
            对于输出物品的id,因为在不断逼近结果的过程中,排序的结果也不断在调整
            所以我们最后的得到的排序结果的前k个就是答案。
    */
    #include <cstdio>
    #include <algorithm>
    using namespace std;
    const int N=101000;
    struct data{int a,b,id;}p[N];
    int n,k,ans[N];
    double m; 
    bool cmp(data a,data b){return a.a-m*a.b>b.a-m*b.b;}
    bool check(double x){
        m=x;
        sort(p+1,p+n+1,cmp);
        double tot_v=0,tot_w=0;
        for(int i=1;i<=k;i++){tot_v+=p[i].a;tot_w+=p[i].b;}
        return tot_v/tot_w>x;
    }
    int main(){
        while(~scanf("%d%d",&n,&k)){
        	double l=0,r=0;
            for(int i=1;i<=n;i++)scanf("%d%d",&p[i].a,&p[i].b),p[i].id=i,r=max(r,(double)p[i].a/p[i].b);
            for(int i=1;i<=100;i++){
                double mid=(l+r)/2;
                if(check(mid))l=mid;
                else r=mid;
            }for(int i=1;i<=k;i++)ans[i]=p[i].id;
    		sort(ans+1,ans+k+1); 
    		for(int i=1;i<k;i++)printf("%d ",ans[i]);
            printf("%d
    ",ans[k]);
        }return 0;
    }

    POJ 3579:Median

    /*
        题目大意:给出一个数列,求两两差值绝对值的中位数。
        题解:因为如果直接计算中位数的话,数量过于庞大,难以有效计算,
        所以考虑二分答案,对于假定的数据,判断是否能成为中位数
        此外还要使得答案尽可能小,因为最小的满足是中位数的答案,才会是原差值数列中出现过的数
        对于判定是不是差值的中位数的过程,我们用尺取法实现。
        对于差值类的题目,还应注意考虑边界,即数列只有一位数的情况。
    */
    #include <cstdio>
    #include <algorithm>
    using namespace std;
    int n,a[100010];
    int main(){
        while(~scanf("%d",&n)){
            for(int i=1;i<=n;i++)scanf("%d",&a[i]);
            sort(a+1,a+n+1);
            int l=0,r=a[n],m=n*(n-1)/4+((n*(n-1)/2)&1),ans=0;
            if(n==1){puts("0");continue;}
            int Ans=0;
            while(l<=r){
                int mid=(l+r)>>1,pre=1,ans=0;
                for(int i=2;i<=n;i++){
                    while(a[i]-a[pre]>mid)pre++;
                    ans+=i-pre;
                }if(ans>=m)r=mid-1,Ans=mid;
                else l=mid+1;
            }printf("%d
    ",Ans);
        }return 0;
    }

    POJ 3685:Matrix

    /*
        题目大意:i和j在N范围内,求i*i+100000×i+j*j-100000×j+i×j的第M大值
        题解:对第M大的数进行二分,然后检验是否满足小于等于这个数的有M个
        在检验的过程中我们发现,当j确定的时候,结果对于i是单调的
        因此我们在每个j的情况下二分i统计满足的数目。
    */
    #include <algorithm>
    #include <cstdio>
    using namespace std;
    typedef long long ll;
    const int C=100000;
    ll f(ll x,ll y){return (x*x+C*x+y*y-C*y+x*y);}
    ll n,m; int T;
    bool check(ll x){
        ll cnt=0;
        for(int i=1;i<=n;i++){
            ll l=1,r=n,ans=0;
            while(l<=r){
                ll mid=(l+r)>>1;
                if(f(mid,i)<=x)ans=mid,l=mid+1;
                else r=mid-1;
            }cnt+=ans;
        }return cnt>=m;
    }
    int main(){
        scanf("%d",&T);
        while(T--){
            scanf("%lld%lld",&n,&m);
            ll l=-C*n,r=n*n+C*n+n*n+n*n,ans=0;
            while(l<=r){
                ll mid=(l+r)>>1;
                if(check(mid))ans=mid,r=mid-1;
                else l=mid+1;
            }printf("%lld
    ",ans);
        }return 0;
    }

    POJ 2010:Moo University - Financial Aid

    /*
        每个物品有两个属性值s和f,从m个物品中选取n个物品
        求f的和小于等于F的情况下s的中位数最大值。
        按照s排序,枚举每个s作为中位数的情况
        那么只要知道前面n/2最小值的和和后面n/2最小值的和就好
        这个可以利用优先队列预处理出来 
    */
    #include <cstdio>
    #include <algorithm>
    #include <queue> 
    using namespace std;
    priority_queue<int> q;
    const int N=100010;
    int n,m,F,pre[N],nxt[N],sum;
    struct data{int s,f;}p[N];
    bool cmp(data a,data b){return a.s>b.s;}
    int main(){
        scanf("%d%d%d",&n,&m,&F);
        for(int i=1;i<=m;i++)scanf("%d%d",&p[i].s,&p[i].f);
        sort(p+1,p+m+1,cmp);
        int base=n/2;
        for(int i=1;i<=base;i++)q.push(p[i].f),sum+=p[i].f;
        for(int i=base+1;i<=m-base;i++){
            pre[i]=sum;
            if(q.top()>p[i].f){
                sum-=q.top();
                q.pop();
                sum+=p[i].f;
                q.push(p[i].f);
            }
        }while(!q.empty())q.pop();sum=0;
        for(int i=m;i>m-base;i--)q.push(p[i].f),sum+=p[i].f;
        for(int i=m-base;i>=base+1;i--){
            nxt[i]=sum;
            if(q.top()>p[i].f){
                sum-=q.top();
                q.pop();
                sum+=p[i].f;
                q.push(p[i].f);
            }
        }int ans=-1;
        for(int i=base+1;i<=m-base;i++){
            if(p[i].f+pre[i]+nxt[i]<=F){
                ans=p[i].s;
                break;
            }
        }printf("%d
    ",ans);
        return 0;    
    }

    POJ 3662:Telephone Lines

    /*
        题目大意:给出点,给出两点之间连线的长度,有k次免费连线,
        所用的费用为免费连线外的最长的长度。
        题解:二分答案,对于大于二分答案的边权置为1,小于等于的置为0,
        则最短路就是超出二分答案的线数,如果小于等于k,则答案是合法的
    */
    #include <cstdio>
    #include <cstring>
    using namespace std;
    const int N=200010,inf=~0U>>2,M=200000;
    int ans=-1,x,S,T,time[N],q[N],size,h,t,n,m,k,ed,dis[N],in[N],nxt[N],w[N],v[N],g[N],u,e,cost;
    void add(int x,int y,int z){v[++ed]=y;w[ed]=z;nxt[ed]=g[x];g[x]=ed;}
    bool spfa(int S,int limit){
        for(int i=1;i<=n;i++)dis[i]=inf,in[i]=0,time[i]=0;
        time[S]=1,dis[S]=0,in[S]=1;
        int i,x,size; q[h=t=size=1]=S;
        while(size){
            for(i=g[x=q[h]],h=(h+1)%M,size--;i;i=nxt[i])if(dis[x]+(w[i]>limit?1:0)<dis[v[i]]){
                dis[v[i]]=dis[x]+(w[i]>limit?1:0);
                if(!in[v[i]]){
                    time[v[i]]++,t=(t+1)%M,size++,in[q[t]=v[i]]=1;
                    if(time[v[i]]>n)return 0;
                }
            }in[x]=0;
        }return dis[T]<=k;
    }
    int main(){
        scanf("%d%d%d",&n,&m,&k);
        memset(v,0,sizeof(v)); memset(nxt,0,sizeof(nxt));
        memset(w,0,sizeof(w)); memset(g,0,sizeof(g)); ed=0;
        for(int i=1;i<=m;i++){
            scanf("%d%d%d",&u,&e,&cost);
            add(u,e,cost);add(e,u,cost);
        }int l=0,r=1000000;T=n;
        while(l<=r){
            int mid=(l+r)>>1;
            if(spfa(1,mid)){ans=mid;r=mid-1;}
            else l=mid+1;
        }return printf("%d
    ",ans),0;
    }

    POJ 1759:Garland

    /*
        题目大意:有n个数字H,H[i]=(H[i-1]+H[i+1])/2-1,已知H[1],求最大H[n],
        使得所有的H均大于0.
        题解:我们得到递推式子H[i]=2*H[i-1]+2-H[i-2],发现H[n]和H[2]成正相关
        所以我们只要二分H[2]的取值,同时计算每个H是否大于等于0即可。
    */
    #include <cstdio>
    int n;
    double H[1010],A,B;
    bool check(double x){
        H[1]=A,H[2]=x;
        for(int i=3;i<=n;i++){
            H[i]=2.0*H[i-1]+2-H[i-2];
            if(H[i]<0)return 0;
        }return B=H[n],1;
    }
    int main(){
        while(~scanf("%d%lf",&n,&A)){
            double l=0,r=A;
            for(int i=0;i<100;i++){
                double mid=(l+r)/2;
                if(check(mid))r=mid;
                else l=mid;
            }printf("%.2f
    ",B);
        }return 0;
    }

    POJ 3484:Showstopper

    /*
        题目大意:给出n个等差数列的首项末项和公差。求在数列中出现奇数次的数。题目保证
        至多只有一个数符合要求。
        题解:因为只有一个数符合要求,所以在数列中数出现次数的前缀和必定有奇偶分界线,
        所以我们二分答案,计算前缀和的奇偶性进行判断,得到该数的位置。
    */
    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    using namespace std;
    typedef long long LL;
    const int N=500010;
    const LL inf=1LL<<33;
    LL X[N],Y[N],Z[N];
    char s[60];
    int n; 
    LL cal(LL x){  
        LL ans=0,t; 
        for(int i=1;i<=n;i++){
            if(x<X[i])continue;  
            t=min(x,Y[i]);  
            ans+=(t-X[i])/Z[i]+1;  
        }return ans;  
    }
    int main(){
        while(gets(s)){
            X[n=1]=0;
            sscanf(s,"%lld %lld %lld",&X[n],&Y[n],&Z[n]);
            if(!X[n])continue;
            memset(s,0,sizeof(s));
            while(gets(s),*s)n++,sscanf(s,"%lld %lld %lld",&X[n],&Y[n],&Z[n]),memset(s,0,sizeof(s));
            LL l=1,r=inf,ans=0;
            while(l<=r){
                LL mid=(l+r)>>1;
                if(cal(mid)&1LL)r=mid-1,ans=mid;
                else l=mid+1;
            }if(!ans)puts("no corruption");
            else printf("%lld %lld
    ",ans,cal(ans)-cal(ans-1));
        }return 0;
    }
    

      

  • 相关阅读:
    同一页面的不同Iframe获取数据
    同一页面的两个Iframe获取数据
    关于从SVN检出项目后,项目名称还是之前修改之前或者项目名称不对问题
    <fieldset>标签
    利用js动态创建<style>
    找换硬币问题 与 0-1背包问题区别
    某种 找换硬币问题的贪心算法的正确性证明
    部分背包问题的贪心算法正确性证明
    从 活动选择问题 看动态规划和贪心算法的区别与联系
    求解两个字符串的最长公共子序列
  • 原文地址:https://www.cnblogs.com/forever97/p/6236789.html
Copyright © 2020-2023  润新知