• 复制书稿(book) (二分,贪心+dp)


    复制书稿(book)

    时间限制: 1 Sec  内存限制: 128 MB
    提交: 3  解决: 1
    [提交][状态][讨论版][命题人:quanxing]

    题目描述

    现在要把m本有顺序的书分给k个人复制(抄写),每一个人的抄写速度都一样,一本书不允许给两个(或以上)的人抄写,分给每一个人的书,必须是连续的,比如不能把第一、第三和第四本书给同一个人抄写。

    现在请你设计一种方案,使得复制时间最短。复制时间为抄写页数最多的人用去的时间。

     

    输入

    第一行两个整数m,k;(k≤m≤500)

    第二行m个整数,第i个整数表示第i本书的页数。

    输出

    共k行,每行两个整数,第i行表示第i个人抄写的书的起始编号和终止编号。k行的起始编号应该从小到大排列,如果有多解,则尽可能让前面的人少抄写。

     

    样例输入

    9 3			
    1 2 3 4 5 6 7 8 9

    样例输出

    1 5
    6 7
    8 9

    提示

    一开始直接用dp,但是发现这道题具有后效性,不能有dp

    典型测试数据:

    10 4

    1 1 1 1 1 1 1 1 1 1

    答案为

    1 1
    2 4
    5 7
    8 10

    用dp的话答案为:

    1 2

    3 4

    5 7

    8 10

    因为考虑钱4个时,dp【4】【2】最优就是 1 2   和3 4,但是因为k行的起始编号应该从小到大排列,如果有多解,则尽可能让前面的人少抄写,导致,之后发现最多为3是,为了让1号是,1号应为1 ,第二个人干3个

    WA的错误解法:

    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <algorithm>
    #include <vector>
    #include <queue>
    #include<deque>
    #include<stack>
    #define inf 0x3f3f3f3f
    using namespace std;
    int a[505];
    int sum[505];
    int dp[505][505];
    int main()
    {
        int m,k;
        scanf("%d %d",&m,&k);
        sum[0]=0;
        memset(dp,inf,sizeof(dp));
        for(int i=1;i<=m;i++)
        {
            scanf("%d",&a[i]);
            sum[i]=sum[i-1]+a[i];
            dp[i][1]=sum[i];
        }
    
        for(int i=2;i<=m;i++)
        {
            for(int j=1;j<=i;j++)
            {
                for(int p=1;p<=min(i-1,k-1);p++)
                {
                    if(dp[i][p+1]>max(dp[j][p],sum[i]-sum[j]))
                    {
                        dp[i][p+1]=max(dp[j][p],sum[i]-sum[j]);
                    }
                }
            }
        }
        int t=m;
        int p=k;
        stack<int>s;
        while(!s.empty()) s.pop();
        s.push(m);
        while(p!=1)
        {
            for(int j=1;j<=t;j++)
            {
                if(dp[t][p]==max(dp[j][p-1],sum[t]-sum[j]))
                {
                    s.push(j+1);
                    s.push(j);
                    t=j;
                    p--;
                    break;
                }
            }
        }
        s.push (1);
        for(int i=1;i<=k;i++)
        {
            cout<<s.top()<<" ";
            s.pop();
            cout<<s.top()<<endl;
            s.pop();
        }
        //cout<<dp[m][k]<<endl;
        return 0;
    }
    View Code

    因为书是不能排序的,所以单调处理,DP死套路:

    1、问什么设什么:

    f[i][j]表示前 i 本书分给 j 个人,

    2、做过5题以上DP的小弱都应该想到,需要第三重循环,我设 k 用来表示*最后一个人拿的第一本书的编号*

    k枚举的范围就是(j->i):因为前面最少要保留 j-1本书分给前面的 j-1个人,最后一个人自己最少也要有一本,所以右边边界就是 i

    3、到这里已经可以把最优解(每人分到的书页的上限)求出来,然后就懵笔了。去翻题解,原来离成功只差一步贪心。从后往前(题目说要前面的人尽可能没那么累),把书扔给各个人就A了。

    还有一些过程中的坑,代码里面告诉你。

    解法一:基本思路:DP方程求出最长花费的时间,然后用贪心的方法,从后向前递归,让后面的人尽量复制多的书,再倒序输出就可以了。

      #include<cstdio>
        #include<cstring>
        int f[510][510];// f[i][j] 表示把 前 i本书 分给 k 个人 
        int ma[510],su[510];
        int n,m;
        int maxx(int x,int y) { return x>y?x:y; } 
        int minn(int x,int y) { return x<y?x:y; } 
        void ff()
        {
            for(int j=2;j<=m;j++)
            {
                for(int i=j;i<=n;i++)
                {
                    for(int k=j;k<=i;k++)//k 为 最后一个人拿到的第一本书的编号 
                    {
                        int an=0;
                        an=maxx(f[k-1][j-1],su[i]-su[k-1]);
                        f[i][j]=minn(f[i][j],an);
                    }
                }
            }
        }
        void pr(int l,int r) //打印当前左右边界内的部分 
        {
            int ss=0;
            for(int i=r;i>=l;i--)
            {
                if(ss+ma[i]>f[n][m])
                {
                    pr(l,i);
                    printf("%d %d
    ",i+1,r);//逆序输出,回溯时才打印 
                    return ; 
                }
                ss+=ma[i];
            }
            printf("%d %d
    ",1,r);//关于第一个人的特殊处理(边界) 
        }
        int main()
        {
            memset(su,0,sizeof(su));//预处理前 N 项和的数组 
            memset(f,63,sizeof(f));
            scanf("%d %d",&n,&m); if(n==0) return 0;
            for(int i=1;i<=n;i++)
            {
                scanf("%d",&ma[i]); 
                su[i]=ma[i]+su[i-1];
                f[i][1]=su[i];
            }
            ff();
            //printf("%d
    ",f[n][m]);
            pr(1,n);
            return 0;
        }
    View Code
    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <algorithm>
    #include <vector>
    #include <queue>
    #include<deque>
    #include<stack>
    #define inf 0x3f3f3f3f
    using namespace std;
    int a[505];
    int sum[505];
    int dp[505][505];
    int main()
    {
        int m,k,i,j;
        scanf("%d %d",&m,&k);
        sum[0]=0;
        memset(dp,inf,sizeof(dp));
        for( i=1;i<=m;i++)
        {
            scanf("%d",&a[i]);
            sum[i]=sum[i-1]+a[i];
            dp[i][1]=sum[i];
        }
        for(i=2;i<=m;i++)
        {
            for(j=1;j<=i;j++)
            {
                for( int p=1;p<=min(i-1,k-1);p++)//记录几个人了,不能超过k个
                {
                    if(dp[i][p+1]>max(dp[j][p],sum[i]-sum[j]))
                    {
                        dp[i][p+1]=max(dp[j][p],sum[i]-sum[j]);
                    }
                }
            }
        }
        int ss=dp[m][k];
    
        //接下来用贪心的方法输出
        stack<int>s;
        while(!s.empty ()) s.pop();
        int r=m,l=m;//分别表示两端点
        int he=0;
        int num=0;
        for(i=m;i>=1;i--,l--)
        {
            he+=a[i];
            if(he>ss)
            {
                s.push(r);
                s.push(l+1);
                he=a[i];
                r=i;
                l=i;
                num++;
                if(num+i==k&&(l==r)) break;
                //每人只能分一本书了
                //这边要判一下
                //5 4 1 1 1 1 1
            }
        }
        if(i!=0)
        {//特殊情况
            for(int j=i;j>=1;j--)
            {
                s.push (j);
                s.push (j);
            }
        }
        else
        {
            s.push(r);
            s.push(l+1);
        }
        for(i=1;i<=k;i++)
        {
            cout<<s.top()<<" ";
            s.pop();
            cout<<s.top()<<endl;
            s.pop();
        }
        return 0;
    }

    解法二:题解告诉我可以用二分来找最优值

       #include<cstdio>
        #include<cstring>
        int n,m,ans;
        int a[510];
        bool ch(int x)
        {
            int su=0,an=0;//an表示能分几个人 
            //printf("%d
    ",x);
            for(int i=n;i>=1;i--)
            {
                if(i==1) an++; //最后一人(最前面的)需要特殊处理 
                if(su+a[i]<=x)
                {
                    su+=a[i];
                }
                else
                {
                    an++;su=a[i];
                }
            }
            if(an<=m) return 1;
            return 0;
        }
        void pr(int l,int r) //打印当前左右边界内的部分 
        {
            int ss=0;
            for(int i=r;i>=l;i--)
            {
                if(ss+a[i]>ans)
                {
                    pr(l,i);
                    printf("%d %d
    ",i+1,r);//逆序输出,回溯时才打印 
                    return ; 
                }
                ss+=a[i];
            }
            printf("%d %d
    ",1,r);//关于第一个人的特殊处理(边界) 
        }
        int main()
        {
            scanf("%d %d",&n,&m); if(n==0) return 0;// 判 0 的坑点,我wa了3次 
            int l=0,r,mid;
            for(int i=1;i<=n;i++)
            {
                scanf("%d",&a[i]);
                r+=a[i]; 
            }
            // 二分模板
            while(l<=r)
            {
                mid=(l+r)/2;
                if(ch(mid))
                {
                    ans=mid; //ans存的就是每个人能分到的页数最大值,用这个来判输出 
                    r=mid-1;
                }
                else l=mid+1;
            }
            //printf("%d
    ",ans);
            pr(1,n);// 打印函数(我用递归来实现的,直接倒推打循环也是可以的) 
            return 0;
        }
  • 相关阅读:
    目前博客园NB团队成员名单
    [征询意见]博客园准备组建在线培训团队
    [调查]园子里有哪些朋友在做开源项目
    都是百度惹的祸
    准备成立NBear开源项目团队—博客园NB团队
    [公告]欢迎各地.NET俱乐部负责人与我联系
    [公告]服务器搬迁
    VS 2005 文本编码小技巧
    Net Framework 2.0 事务处理
    ArgoUML 开源UML 建模工具
  • 原文地址:https://www.cnblogs.com/caiyishuai/p/13270999.html
Copyright © 2020-2023  润新知