复制书稿(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\n",i+1,r);//逆序输出,回溯时才打印 
                return ; 
            }
            ss+=ma[i];
        }
        printf("%d %d\n",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\n",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\n",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\n",i+1,r);//逆序输出,回溯时才打印 
                return ; 
            }
            ss+=a[i];
        }
        printf("%d %d\n",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\n",ans);
        pr(1,n);// 打印函数(我用递归来实现的,直接倒推打循环也是可以的) 
        return 0;
    }

 

posted on 2018-05-18 15:06  蔡军帅  阅读(1427)  评论(0编辑  收藏  举报