【二分搜索】

来源:互联网 发布:沈阳数据恢复中心 编辑:程序博客网 时间:2024/06/05 17:08


/*题意:给出n条线段,以米的单位给出,小数点后两位(精确到厘米),要你对这些线段裁剪,裁剪出m条等长的线段,并且让这些线段尽可能长另外线段的长度不能小于1厘米,如果筹不够m条,输出0.00POJ  1064*/#include<cstdio>#include<cstring>using namespace std;const int maxn=10010;double num[maxn];double eps=1e-5;int main(){    int n,k;    while(~scanf("%d%d",&n,&k)){        double maxvalue=0;        for(int i=0;i<n;i++){            scanf("%lf",num+i);            if(num[i]>maxvalue)                maxvalue=num[i];        }        double lp=0,rp=maxvalue;        while(rp-lp>eps){            double mid=(rp+lp)/2;            int sum=0;            for(int i=0;i<n;i++){                sum+=num[i]/mid;            }            if(sum>=k)                lp=mid;            else                rp=mid;        }        printf("%0.2lf\n",int(rp*100)*0.01);    }}




//用整数来二分#include<cstdio>#include<cstring>#include<algorithm>using namespace std;#define N 10010#define MAX 0x3f3f3f3fint a[N];int main(){    int n,m;    double len;    while(~scanf("%d%d",&n,&m)){        int Max=0;        for(int i=0;i<n;i++){            scanf("%lf",&len);            a[i]=len*100;            Max=max(Max,a[i]);        }        int low=1,high=Max;        int res=0;        while(low<=high){            int mid=(low+high)>>1;            int sum=0;            for(int i=0;i<n;i++)                sum+=a[i]/mid;            if(sum>=m)                res=max(res,mid),low=mid+1;            else                high=mid-1;        }        printf("%.2lf\n",(double)res/100.0);    }}



/*一条线段上有 n 个点,选取 m 个点,使得相邻点之间的最小距离值最大。POJ  2456*/#include<cstdio>#include<cstring>#include<algorithm>using namespace std;int L,n,m,pos[100005];bool can(int l){    int cnt=1,cur=pos[0];    for(int i=1;i<n;i++){        while(i<n&&pos[i]-cur<l)            i++;        cur=pos[i];        if(i<n&&++cnt==m)            return true;    }    return false;}int main(){    while(~scanf("%d%d",&n,&m)){        for(int i=0;i<n;i++)            scanf("%d",&pos[i]);        sort(pos,pos+n);        int l=1,r=pos[n-1]-pos[0];        while(l<=r){            int mid=(l+r)/2;            if(can(mid))                l=mid+1;            else                r=mid-1;        }        printf("%d\n",r);    }}

这道题目是一道0-1分数规划求最优值。方法是一个二分搜索+贪心的题目。出这道题目就是告诉大家二分不仅可以查找,还可以搜索一个更优值。要使得单位重量的价值最大,则其最大不超过单个中最大的单位重量的价值,最小当然不小于0.那么我们就这一在0--最大单位重量的价值中间找一个值ans,使得ans为满足题目条件的最大值。如果满足条件,则可以找更大的。设置一个条件。既二分搜索、从n个物品中找k个使得k个的价值和/质量和>=ans为了使得ans尽可能的大,那么这里就要贪心选择。#include<cstdio>#include<algorithm>using namespace std;double wi[10005],vi[10005],pi[10005];int n,k;bool cmp(double a,double b){    return a>b;}int check(double x){    for(int i=0;i<n;i++){//p[i] 代表的是  自己本省的价值-物品重量*最高单价        pi[i]=vi[i]-wi[i]*x;    }    double y=0;    sort(pi,pi+n,cmp);    //p[i] 由大到小排序。    for(int i=0;i<k;i++){           y+=pi[i];    }    return y>=0;}double  reach (double m){// 二分搜索    double l=0,r=m,mid;    for(int i=0;i<100;i++){        mid=(r+l)/2;        if(check(mid))            l=mid;        else            r=mid;    }    return l;}int main(){    while(~scanf("%d%d",&n,&k)){    double ma=0;    for(int i=0;i<n;i++){         scanf("%lf%lf",&wi[i],&vi[i]);         double ant=vi[i]/wi[i];         if(ant>ma)             ma=ant;//选择出单价最高的。    }    printf("%0.2lf\n",reach(ma));    }}


/*POJ 3104【题意】:给出n件刚洗完的衣服,每件衣服有一个属性ai,表示改衣服在自然风干的条件需要ti分钟。现在有一台烘干机,每分钟你可以选择把任意一件衣服放进去,那么这件衣服风干的时间就减少k分钟,而每分钟对于不在烘干机的衣服,风干时间都减少1。问最少用多少时间使所有衣服都干了。【题解】:直接二分答案,然后判断答案的正确性。               假设当前二分的答案为 t,那么:               对于ai <= t的衣服,显然让它们自然风干就可以了。               对于ai > t的衣服,我们需要知道该衣服最少用多少次烘干机。               设该衣服用了x1分钟风干,用了x2分钟烘干机。               那么有 x1 + x2 = t 和 ai <= x1 + x2 * k,联立两式可得 x2 >= (ai - t) / (k - 1),即最少使用次数为[(ai - t) / (k - 1)] 的最小上界。                           ************************************************            特别注意 k不能等于1,k等于分母就等于0了            ****************************************************               最后,判断一下总使用次数是否少于 t 即可。*/#include<cstdio>#include<cstring>#define N 100005using namespace std;int time[N];int n,k;bool check(int t){    int cnt=0;    for(int i=0;i<n;i++){        if(time[i]<t) continue; //自然风干的时间比其要短,那就让它自然风干        double temp=(double)(time[i]-t)/(k-1);//保证一个物体占用甩干机的时间最短,即为(time[i]-t)/(k-1)        cnt+=(int)temp;        if(temp-(int)temp>0)            ++cnt;//向上取整。如果temp为3.4 ,那么cnt就应该为4        if(cnt>t)            return false;    }    return true;}int main(){    int low,high,mid,ans;    while(~scanf("%d",&n)){        high=0;low=0;        for(int i=0;i<n;i++){            scanf("%d",&time[i]);            if(time[i]>high)                high=time[i];        }        scanf("%d",&k);        if(k==1){  //k不能为1,为1直接输出            printf("%d\n",high);            continue;        }        ans=high;        while(low<=high){            mid=low+(high-low)*0.5;            if(check(mid)){                ans=mid;                high=mid-1;            }            else                low=mid+1;        }        printf("%d\n",ans);    }}



/*求一个有n个正整数组成的序列,给定整数S,求长度最短的连续序列,使得它们的和大于等于SPOJ  3061*/#include<cstdio>#include<cstring>using namespace std;#define maxn 100005int n;int a[maxn],sum[maxn],s;bool check(int x){    for(int i=1;i+x-1<=n;i++){        if(sum[i+x-1]-sum[i-1]>=s)  return true;    }    return false;}int  solve(){    int l=1,r=n;    while(l<=r){        int mid=(l+r)>>1;        if(check(mid))  r=mid-1;        else l=mid+1;    }    return l;}int main(){    int t;    scanf("%d",&t);    while(t--){        memset(sum,0,sizeof(sum));        scanf("%d%d",&n,&s);        for(int i=1;i<=n;i++){            scanf("%d",&a[i]);            sum[i]+=sum[i-1]+a[i];//用sum[i]数组存储从1 到 i 的和        }        if(sum[n]<s)            printf("0\n");        else            printf("%d\n",solve());    }}

/*第二种是假设从s位置开始到t的和大于S,并且s + 1 到t' 的和大于S,则t‘ > t 由此可以跑一次o(n)解决POJ 3061 */#include<cstdio>#include<algorithm>using namespace std;#define maxn 100005int n,S;int a[maxn];void solve(){    int s=1,sum=0,pos=1;    int ans=n+1;    for(;;s++){        while(sum<=S&&pos<=n)            sum+=a[pos++];        if(sum<S) break;        sum-=a[s];        ans=min(ans,pos-s);    }    printf("%d\n",ans==n+1?0:ans);}int main(){    int t;    scanf("%d",&t);    while(t--){        scanf("%d%d",&n,&S);        for(int i=1;i<=n;i++){            scanf("%d",&a[i]);        }        solve();    }}


/*题意:n头牛站成线,有朝前有朝后的的,然后每次可以选择大小为k的区间里的牛全部转向,会有一个最小操作m次使得它们全部面朝前方。问:求最小操作m,再此基础上求k。'POJ  3276*/#include<cstdio>#include<cstring>#include<algorithm>using namespace std;int N;int dir[5005];//牛的方向 0:F 1: Bint f[5005];// 区间[i,i+K-1] 是否进行反转//固定K,求相应的最小操作回数//无解返回-1int clac(int K){    memset(f,0,sizeof(f));    int res=0;    int sum=0; //f 的和    for(int i=0;i+K<=N;i++){        //计算区间[i,i+K-1]        if((dir[i]+sum)%2!=0){            //前端的牛朝后方            res++;            f[i]=1;        }        sum+=f[i];        if(i-K+1>=0){            sum-=f[i-K+1];        }    }    //检查剩下的牛是否有朝着后方的情况    for(int i=N-K+1;i<N;i++){        if((dir[i]+sum)%2!=0)  return -1;        if(i-K+1>=0)            sum-=f[i-K+1];    }    return res;}void solve(){    int K=1,M=N;    for(int k=1;k<=N;k++){        int m=clac(k);        if(m>=0 &&M>m){            M=m;            K=k;        }    }    printf("%d %d\n",K,M);}int main(){    char c;    while(~scanf("%d%*c",&N)){        for(int i=0;i<N;i++){            scanf("%c%*c",&c);            if(c=='F')                dir[i]=0;            else                dir[i]=1;        }        solve();    }}




/*题目大意:某人读一本书,要看完所有的知识点,这本书共有P页,第i页恰好有一个知识点ai,(每一个知识点都有一个整数编号)。全书同一个知识点可能会被提到多次,他希望阅读其中一些连续的页把所有知识点都读到,给定每页所读到的知识点,求最少的阅读页数。思路:和上一题一样,也是尺取法的应用。假设从某一页s开始阅读,为了覆盖所有的知识点读到t页,这样的话如果从s+1开始阅读,那么必须读到t'>=t位置,故可以用尺取法。用上Map来统计次数,取出前一项要把对应的知识点的编号次数-1.详见代码。POJ  3320*/#include<cstdio>#include<set>#include<map>#include<algorithm>using namespace std;int P;int a[1000000+10];map<int,int> countx;  //知识点---出现次数 映射set<int> all;void solve(){    all.clear();    countx.clear();    for(int i=0;i<P;i++)        all.insert(a[i]);    int n=all.size();    int s=0,t=0,num=0;    int res=P;    while(true){        while(t<P&&num<n){            if(countx[a[t++]]++==0){  //出现新的知识点                num++;            }        }        if(num<n) break;        res=min(res,t-s);        if(--countx[a[s++]]==0){  //某个知识点出现次数为0            num--;        }    }    printf("%d\n",res);}int main(){        while(~scanf("%d",&P)){            for(int i=0;i<P;i++)                scanf("%d",&a[i]);            solve();        }}

/*题意是说,对四个数列中的数,每一列取一个,求取出来的四个数的和为零的组合个数。POJ  2785*/#include<cstdio>#include<algorithm>#define MAXN 4005using namespace std;int n;int A[MAXN],B[MAXN],C[MAXN],D[MAXN];int CD[MAXN*MAXN];void solve(){    for(int i=0;i<n;i++){        for(int j=0;j<n;j++){            CD[i*n+j]=C[i]+D[j];        }    }    sort(CD,CD+n*n);    long long res=0;    for(int i=0;i<n;i++){        for(int j=0;j<n;j++){            int cd=-(A[i]+B[j]);            res+=upper_bound(CD,CD+n*n,cd)-lower_bound(CD,CD+n*n,cd);        }    }    printf("%lld\n",res);}int main(){    while(~scanf("%d",&n)){        for(int i=0;i<n;i++)            scanf("%d%d%d%d",&A[i],&B[i],&C[i],&D[i]);        solve();    }}


/*【题目大意】:给出n辆赛车距离终点的距离,每秒钟会前进1米,现在给出m个可以加速k的加速器,每次每辆车只能使用一次加速器,下一个时间点加速器可以重复使用。问所有赛车到达终点的最短时间。POJ   3232*/#include<cstdio>int rider,dis[100005],acc,k;bool check(int m){    long long tot=(long long)acc*m;    long long cnt=0;    for(int i=0;i<rider;i++){        if(dis[i]<=m) continue; //dis[i] <= m的话该车手自己就可以跑到终点        int tmp=(dis[i]-m)/(k-1)+((dis[i]-m)%(k-1)!=0);//tmp就是当前车手需要的加速次数,向上取整        if(tmp>m) return false; //注意!!这说明即使他一直在加速,一个加速器都无法满足该车手        cnt+=tmp;        if(cnt>tot) return false;//一旦超过总加速次数,说明次数不够    }    return true;}int main(){    int test;    scanf("%d",&test);    while(test--){        int maxi=0;        scanf("%d",&rider);        for(int i=0;i<rider;i++){            scanf("%d",&dis[i]);            if(dis[i]>maxi) maxi=dis[i];        }        scanf("%d%d",&acc,&k);        if(k>1){            int l=1,r=maxi,mid;            while(l<=r){                mid=(l+r)>>1;                if(check(mid))                    r=mid-1;                else                    l=mid+1;  //注意!当check失败的时候,意味着需要更大的答案            }             printf("%d\n",l);        }        else printf("%d\n",maxi);    }}


0 0