挑战程序设计竞赛 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\n",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\n",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\n",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\n",r);continue;} 
        while(l<=r){
            int mid=(l+r)>>1;
            //printf("%d %d %d\n",l,r,mid);
            if(check(mid))ans=mid,r=mid-1;
            else l=mid+1;
        }printf("%d\n",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\n",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\n",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\n",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\n",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\n",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\n",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\n",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\n",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\n",ans,cal(ans)-cal(ans-1));
    }return 0;
}

  

posted @ 2016-12-30 14:26  forever97  阅读(211)  评论(0编辑  收藏  举报