洛谷 P1280 尼克的任务

传送门

https://www.cnblogs.com/violet-acmer/p/9852294.html

题意:

  尼克一天需工作n个时刻,在这一天中有k个任务,给出每个任务开始时间和持续时间。

  如果尼克在某一时刻已经结束了上一个任务,他就必须在下一个时刻选择一个开始时间在下一个时刻的任务来做。

  求最大空闲时间?

  这时什么意思呢?

  来,找个样例解释一下:

  Simple Input:

  5 4

  1 2

  2 2
  3 2
  4 2

  Simple Output:

  1

  在1时刻,尼克是有空的,所以需要选择任务1来做,选择任务1后,一直到2时刻才结束任务。

  虽然2时刻结束了任务,但不能立即找开始时刻为2的任务去做(让人家休息一下啊),而是去找开始时刻在1任务结束时刻下一时刻的任务去做,也就是找开始时刻大于2的最近的任务去做,找到了任务3,任务3完成后,去找开始时刻大于4的最近的任务去做,发现,没了(啊啊啊,终于结束了)。

  所以,尼克在这n个时刻共完成了任务1,3,用了时刻1,2,3,4,而尼克这一天的总工作时间是5个时刻,所以,尼克空暇了1个时刻。

啊啊啊,一开始题意理解错了,wa的不要不要的

题解:

  相关变量解释:

    work[maxn].................................................work[i].first,work[i].second : 分别表示第 i 个工作的开始与结束时刻

    start[maxn].................................................存储 n 个任务中不同的开始时刻,按开始时刻升序排列

    dp[maxn]....................................................dp[i]:表示从开始时刻 i 到结束时刻 n 需要工作的最少时间

  根据dp[]代表的含义,则原问题可以转变为求 dp[ 开始时刻,结束时刻 ] 所需工作的最少时间,那最大空暇时间就是 n-dp[ 开始时刻,结束时刻 ]

  步骤:

    (1):先将work[ ] 按照开始时刻从小到大排序

    (2):求出最后时刻的最优解,因为最后时刻的最优解就是其本身的工作时间

    (3):从后往前遍历,在往前遍历的过程中,只需要当前任务结束时刻之后的最近的工作任务的最优解,而在来到当前任务的时候,其之后工作任务的最优解一定是求好的

  分析:为什么可以用DP解决呢?(非官方)

    1.满足最优子结构性质:

      如上所述,dp[i]表示的区间[ i , n ]的最少的工作时间,问题 dp[ 开始时刻,结束时刻 ] 的最优解,也意味着其包含的子问题 dp [ 某开始时刻,结束时刻 ] 的最优解,所

  以此问题满足最优子结构问题。

    2.满足无后效性性质:

      在从后往前遍历的过程中,求出当前任务的最优解后,再当前任务之前任务的最优解时,只关注当前任务的最优解这个值,而不关注当前这个最优解是如何得到的,

    也就是说和之前是采取哪种手段或经过哪条路径演变到当前的这个状态,没有关系。

  满足以上两个性质,便可以实用DP了。

先把AC代码献上,题解晚上补:

 1 #include<iostream>
 2 #include<algorithm>
 3 #include<cstdio>
 4 #include<cstring>
 5 using namespace std;
 6 #define mem(a,b) memset(a,b,sizeof a)
 7 #define P pair<int ,int >
 8 const int maxn=1e4+50;
 9 
10 int n,k;
11 P work[maxn];
12 int start[maxn];
13 int dp[maxn];
14 bool cmp(P _a,P _b){
15     return _a.first < _b.first;
16 }
17 int Time(P p){//某工作的持续时间
18     return p.second-p.first+1;
19 }
20 void Solve()
21 {
22     sort(work+1,work+k+1,cmp);//按开始时刻升序排列
23     sort(start+1,start+k+1);
24     int tot=unique(start,start+k+1)-start;//去重:对于不同工作但具有相同的开始时刻,只保留一个
25     start[tot]=10010;
26 
27     int p=k;
28     for(int i=tot-1;i >= 1;--i)
29     {
30         while(work[p].first == start[i])//找到所有的开始时刻为start[i]的工作
31         {
32             //在 [i+1,tot+1)范围内查找当前工作结束时刻之后的开始时刻最近的工作,注意用upper_bound()
33             //如果当前工作结束时刻后没有工作了,那么dp[ ]只需加其本身的持续时刻,此时 t=tot
34             //而 start[tot]=10010,dp[10010]=0,这也是我为什么给start[tot]赋值为10010的原因
35             int t=upper_bound(start+i+1,start+tot+1,work[p].second)-start;
36             if(dp[start[i]] == 0)
37                 dp[start[i]]=Time(work[p])+dp[start[t]];
38             else
39                 dp[start[i]]=min(dp[start[i]],Time(work[p])+dp[start[t]]);
40             p--;
41         }
42     }
43     printf("%d
",n-dp[start[1]]);
44 }
45 
46 int main()
47 {
48     scanf("%d%d",&n,&k);
49     mem(dp,0);
50     for(int i=1;i <= k;++i)
51     {
52         int a,b;
53         scanf("%d%d",&a,&b);
54         work[i]=P(a,a+b-1);
55         start[i]=a;//记录不同工作的开始时刻,可能有重复的,这也是为什么Solve()中需要将其去重
56     }
57     work[0]=P(0,0);
58     Solve();
59 }
60 /**
61 5 4
62 1 2
63 2 2
64 3 2
65 4 2
66 **/
View Code
原文地址:https://www.cnblogs.com/violet-acmer/p/9868771.html