【二分搜索】


/*
题意:给出n条线段,以米的单位给出,小数点后两位(精确到厘米),要你对这些线段裁剪,裁剪出m条等长的线段,并且让这些线段尽可能长另外线段的长度不能小于1厘米,如果筹不够m条,输出0.00
POJ  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 0x3f3f3f3f
int 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 100005
using 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,求长度最短的连续序列,使得它们的和大于等于S
POJ  3061
*/
#include<cstdio>
#include<cstring>

using namespace std;
#define maxn 100005
int 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 100005
int 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: B
int f[5005];// 区间[i,i+K-1] 是否进行反转
//固定K,求相应的最小操作回数
//无解返回-1
int 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 4005
using 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);

    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值