挑战程序设计竞赛 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; }
愿你出走半生,归来仍是少年