尼克的任务【DP经典】

问题描述

尼克每天上班之前都连接上英特网,接收他的上司发来的邮件,这些邮件包含了尼克主管的部门当天要完成的全部任务,每个任务由一个开始时刻与一个持续时间构成。
尼克的一个工作日为N分钟,从第一分钟开始到第N分钟结束。当尼克到达单位后他就开始干活。如果在同一时刻有多个任务需要完成,尼克可以任选其中的一个来做,而其余的则由他的同事完成,反之如果只有一个任务,则该任务必需由尼克去写成,假如某些任务开始时刻尼克正在工作,则这些任务也由尼克的同事完成。如果某任务于第P分钟开始,持续时间为T分钟,则该任务将在第P+T-1分钟结束。
写一个程序计算尼克应该如何选取任务,才能获得最大的空暇时间。

输入格式

输入数据第一行包含两个用空格隔开的整数N和K,1≤N≤10000,1≤K≤10000,N表示尼克的工作时间,单位为分,K表示任务总数。
接下来共有K行,每一行有两个用空格隔开的整数P和T,表示该任务从第P分钟开始,持续时间为T分钟,其中1≤P≤N,1≤P+T-1≤N。

输出格式

输出文件仅一行包含一个整数表示尼克可能获得的最大空暇时间。

样例输入 1

15 6
1 2
1 6
4 11
8 5
8 1
11 5

样例输出 1

4

样例输入 2

20 7
2 10
2 2
5 6
5 5
11 5
11 2
14 4

样例输出 2

8

分析:哇,这题洛谷标签是绿的,我都没过我能自己做出来这道题。。。
闲话不多说,来说说状态转移方程。这题如果读懂了,其实读者会明白就是在一条线段上找出若干个不相交的线段(注意第一条必要),使除这些线段覆盖的点之外的所有点的个数最大,输出。如下图(样例,画的有点丑。。。):
这里写图片描述
选择1到6与8到12两条线段可获得最大个数,4。
那么这题怎么做呢?和DP套路一样,状态,阶段,决策,方程(忽视阶段与决策)。
状态:设f【i】表示i到n的最大空闲值(这里不是每一点都要DP,每一点都DP方程有点难写,或者根本写不出来)
方程:f【i】=max(f【k】+k-i-1)(i+1<=k<=n&&k是某个任务的起始点&&i是某个任务的起始点)
这个方程还是比较好想的(虽然我想了2小时。。。),对于每一个任务,你要么选择做他,要么不选择做,其中选出最大值就好了。如果看不懂描述,可以看看代码,代码应该更清晰一点。

#include<cstdio>  
#include<iostream>  
#include<algorithm>  
#include<cstring>  
#include<iostream>
#include<cmath>
#include<queue>
#include<stack>
#include<set>
#include<vector>
#include<map>
using namespace std;
int f[10010];
bool flag[10010];
struct Node
{
    int Begin;//这个任务的起始点 
    int End;//这个任务的结束点 
}a[10010];
inline bool cmp(Node j,Node k)
{
    return j.End>k.End;//sort排个序 
}
inline int _max(int c,int b)
{
    if(c>b) return c;
    return b;
}
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i=1;i<=k;i++)
    {
        int p,t;
        scanf("%d%d",&p,&t);
        t=p+t-1;//题目所给条件 
        a[i].Begin=p;//赋值 
        a[i].End=t;
        flag[p]=1;//初始化 
    }
    sort(a+1,a+k+1,cmp);//排序 
    for(int i=1;i<=k;i++)
    {
        int sum=0,j;
        for(j=a[i].End+1;j<=n;j++)//如果选择做这个任务,就要从这个任务的结束点开始向后DP,碰到了一个任务就必须做 
        {
            if(flag[j]==1)//碰到了 
            {
                sum=f[j];//赋值 
                break;//必须结束,因为这个任务不管你选不选都必须选 
            }
        }
        f[a[i].Begin]=_max(f[a[i].Begin],sum+j-a[i].End-1);//由于我们直接枚举的任务,所以不需要判断i是否是某个任务的起始点
        //直接赋值就好了 ;如果选, f[a[i].Begin]= sum+j-a[i].End-1。sum代表选了这个任务之后
        // 从下一个必做的任务开始可获得的最大空闲时间,而下一个必做的任务
        //与当前的任务相差的就是j-a[i].End-1了,要加上去 
    }
    int ans,i;
    for(i=1;i<=n;i++)
    {
        if(flag[i]==1)//第一个任务必做 
        {
            ans=f[i];
            break;//同理必须结束 
        }
    }
    printf("%d",ans+i-1);//注意第一个任务有可能不是从1开始的,我们还要加上第一个任务的起始点-1到1的点的个数 
    return 0;
}

如果还有什么不懂,请随时留言评论,有错也欢迎指出,谢谢各位!

posted @ 2018-03-30 18:10  最爱丁珰  阅读(40)  评论(0编辑  收藏  举报