您的位置:首页 > 其它

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

2016-12-30 14:26 323 查看

【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
;
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
;
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
;
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
;
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
;
int n,k,ans
;
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
,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
,nxt
,sum;
struct data{int s,f;}p
;
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
,q
,size,h,t,n,m,k,ed,dis
,in
,nxt
,w
,v
,g
,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
,
使得所有的H均大于0.
题解:我们得到递推式子H[i]=2*H[i-1]+2-H[i-2],发现H
和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
,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
,Y
,Z
;
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
,&Y
,&Z
);
if(!X
)continue;
memset(s,0,sizeof(s));
while(gets(s),*s)n++,sscanf(s,"%lld %lld %lld",&X
,&Y
,&Z
),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;
}


  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: