P1280 尼克的任务

P1280 尼克的任务

题目描述

尼克每天上班之前都连接上英特网,接收他的上司发来的邮件,这些邮件包含了尼克主管的部门当天要完成的全部任务,每个任务由一个开始时刻与一个持续时间构成。

尼克的一个工作日为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

 

提示:

由后面状态更新前面状态的DP。

(填表法和刷表法)

设计状态f【i】表示在i~T(T为总时间)的时间内的最大闲暇时间

状态转移方程:

如果时间点i处没有任务,则f【i】= f【i+1】+1;

f【i】= f【i+1】(前面的空闲时间)+1(现在的空闲时间);

如果时间点i处有任务,则f【i】=max(f【i+任务k的持续时间】); //此处k = 1~时间点i处的任务总个数

所以很容易发现,想要知道f【i】,必须先求出 f【比i大的时间点】,所以确定本题倒推:由f【T】递推到f【1】。

那么如何记录下 时间点i处的每个任务的信息呢,直接将“任务(开始时间、持续时间)”定义成结构体。输入完毕后,以每个任务的开始时间为关键字 从大到小排序。这样就使得 开始时间相同的任务 靠到了一起,并且接下来dp时直接从下标1开始做,就保证了倒推(时间从后往前,因为排过序)。

 
 
 1 #include <bits/stdc++.h>
 2 const int INFINITE=0x3fffffff;
 3 const int MAXN=1e4+10;
 4 using namespace std;
 5 struct taskNode{
 6     int s;//start
 7     int l;//last
 8     taskNode(int s,int l){
 9         this->s=s;
10         this->l=l;
11     } 
12     taskNode(){}
13     bool operator <(const taskNode &p1) const{
14         if(this->s==p1.s) return this->l>p1.l;
15         return this->s>p1.s;
16     }
17 }task[MAXN];//写了有参构造函数之后,空构造函数没有了 
18 int f[MAXN],sch[MAXN];
19 int t,n;
20 
21 void print();
22 void init(){
23     cin>>t>>n;
24     for(int i=1;i<=n;i++){
25         cin>>task[i].s>>task[i].l;
26         sch[task[i].s]++;
27     } 
28     sort(task+1,task+n+1);
29 }
30 
31 void dp(){
32     memset(f,0,sizeof(f));
33     for(int i=t,now=1;i>=1;i--){
34         if(sch[i]==0) f[i]=f[i+1]+1;
35         else for(int j=1;j<=sch[i];j++,now++){
36             f[i]=max(f[i],f[i+task[now].l]);
37         }
38     }
39 }
40 
41 int main(){
42     //freopen("in.txt","r",stdin);
43     init();
44     
45     dp();
46     //print();
47     cout<<f[1]<<endl; 
48     return 0;
49 } 
50 void print(){
51     for(int i=1;i<=n;i++){
52         cout<<task[i].s<<" "<<task[i].l<<endl;
53     }
54     cout<<endl;
55     for(int i=1;i<=t;i++){
56         cout<<f[i]<<" ";
57     }
58     cout<<endl;
59 }

关于上面代码说几点:

 1、第17行 }task[MAXN]; 这样是以无参构造函数初始化了MAXN个变量,已经new出来了。

写了有参构造函数之后,空构造函数没有了 ,所以写了构造函数没写无参,这里会发生错误

2、第2,3行常量的定义,const int MAXN=1e4+10;

3、now这个变量是为了task从上访问到下,所以这是方式可以记一下,很多位置都会用到

4、变量在知道意思的情况下尽量写的简单点吧

 

 

 

 
代码:
1、普通DP
 1 /*
 2 肯定要思路理清楚在写啊
 3 不然只是浪费时间
 4 f[i]表示表示在i~t(t为总时间)的时间内的最大闲暇时间
 5 f[i]初始化为-1
 6 search(int i,int now) 表示i分钟的时候下一条要用到的是now这条记录 
 7 */ 
 8 #include <bits/stdc++.h>
 9 const int INFINITE=0x3fffffff;
10 const int MAXN=1e4+10;
11 using namespace std;
12 struct taskNode{
13     int s;//start
14     int l;//last
15     taskNode(int s,int l){
16         this->s=s;
17         this->l=l;
18     } 
19     taskNode(){}
20     bool operator <(const taskNode &p1) const{
21         if(this->s==p1.s) return this->l>p1.l;
22         return this->s>p1.s;
23     }
24 }task[MAXN];//写了有参构造函数之后,空构造函数没有了 
25 int f[MAXN],sch[MAXN];
26 int t,n;
27 
28 void print();
29 void init(){
30     cin>>t>>n;
31     for(int i=1;i<=n;i++){
32         cin>>task[i].s>>task[i].l;
33         sch[task[i].s]++;
34     } 
35     sort(task+1,task+n+1);
36 }
37 
38 void dp(){
39     memset(f,0,sizeof(f));
40     for(int i=t,now=1;i>=1;i--){
41         if(sch[i]==0) f[i]=f[i+1]+1;
42         else for(int j=1;j<=sch[i];j++,now++){
43             f[i]=max(f[i],f[i+task[now].l]);
44         }
45     }
46 }
47 
48 int main(){
49     //freopen("in.txt","r",stdin);
50     init();
51     
52     dp();
53     //print();
54     cout<<f[1]<<endl; 
55     return 0;
56 } 
57 void print(){
58     for(int i=1;i<=n;i++){
59         cout<<task[i].s<<" "<<task[i].l<<endl;
60     }
61     cout<<endl;
62     for(int i=1;i<=t;i++){
63         cout<<f[i]<<" ";
64     }
65     cout<<endl;
66 }

最最最主要,写成递推表达式,那么既能DP又能记忆化搜索

1、第24行, 如果没有无参构造函数,}task[MAXN];会出错,说明这句话是用空构造函数初始化,已经初始化有内存了

2、写了有参构造函数之后,默认的无参构造函数没有了

3、结构体里面的符号重载

4、这个题目为什么从后往前推而不是从前往后推,看上面图,后推前能确保正确,前推后却不一定能

5、 记忆化递归和DP只是递推公式的运用的方向不一样,其它都是一样的,而且看测试耗时,两个的耗时几乎相同

6、其实从前往后推还是从后往前推,真正的根源就是递推公式,看递推公式两个公式是不是对的:

设计状态f【i】表示在i~T(T为总时间)的时间内的最大闲暇时间

T是固定的,相当于T是起点

从T反过来思考,所有的问题都迎刃而解

状态转移方程:

如果时间点i处没有任务,则f【i】= f【i+1】+1;

如果时间点i处有任务,则f【i】=max(f【i+任务k的持续时间】); //此处k = 1~时间点i处的任务总个数

所以很容易发现,想要知道f【i】,必须先求出 f【比i大的时间点】,所以确定本题倒推:由f【T】递推到f【1】。

  

 

 

2、递归

 1 /* 
 2 肯定要思路理清楚在写啊
 3 不然只是浪费时间
 4 f[i]表示表示在i~t(t为总时间)的时间内的最大闲暇时间
 5 f[i]初始化为-1
 6 search(int i,int now) 表示i分钟的时候下一条要用到的是now这条记录
 7 所以最开始search的是(1,1)
 8 递归终止条件:
 9 如果i>t return 0;
10 */
11 
12 
13 //递归30分 通过1 3 4 三个点,其它都超时 
14 #include <bits/stdc++.h>
15 const int INFINITE=0x3fffffff;
16 const int MAXN=1e4+10;
17 using namespace std;
18 vector<int> vct[MAXN];//存结束时间的下一个 
19 int sch[MAXN]; 
20 int f[MAXN];
21 int t,n;
22 
23 
24 void init(){
25     cin>>t>>n;
26     for(int i=1;i<=n;i++){
27         int s,l;
28         cin>>s>>l;
29         sch[s]++;
30         vct[s].push_back(s+l);
31     }
32 }
33 
34 int search(int i){
35     if(i>t) return 0;
36     else if(sch[i]==0){
37         return search(i+1)+1;
38     }else if(sch[i]!=0){
39         int maxv=0;
40         for(int j=0;j<sch[i];j++){
41             int tmp=search(vct[i][j]);
42             if(tmp>maxv) maxv=tmp;
43         }
44         //cout<<maxv<<endl;
45         return maxv;
46     }
47 }
48 
49 int main(){
50     //freopen("in.txt","r",stdin);
51     //freopen("testdata.in","r",stdin);
52     init();
53     cout<<search(1)<<endl; 
54     return 0;
55 }

1、肯定要由易到难,先写递归,再写记忆化递归

2、递归肯定要想清楚了再写,把递推公式想清楚

3、 递归递归30分, 只能通过1 3 4 三个点,其它都超时 

4、写递归和写记忆化递归,存储结构发生了变化

5、这题的暴力是可行的,统计一根根木棒,遇到有新的木棒就把它加上,后面来实现

 

3、记忆化递归

 

 1 /* 
 2 肯定要思路理清楚在写啊
 3 不然只是浪费时间
 4 f[i]表示表示在i~t(t为总时间)的时间内的最大闲暇时间
 5 f[i]初始化为-1
 6 search(int i,int now) 表示i分钟的时候下一条要用到的是now这条记录
 7 所以最开始search的是(1,1)
 8 递归终止条件:
 9 如果i>t return 0;
10 */
11 
12 
13 //记忆化递归和dp只是方向不同罢了 
14 //由浅入深,有递归到记忆化递归
15 //不管是dp还是递归,都要把递推方程找出来,也就是都要知道怎么解
16 //因为递归和dp只是解的形势不同而已 
17 #include <bits/stdc++.h>
18 const int INFINITE=0x3fffffff;
19 const int MAXN=1e4+10;
20 using namespace std;
21 vector<int> vct[MAXN];//存结束时间的下一个 
22 int sch[MAXN]; 
23 int f[MAXN];
24 int t,n;
25 
26 
27 void init(){
28     cin>>t>>n;
29     for(int i=1;i<=n;i++){
30         int s,l;
31         cin>>s>>l;
32         sch[s]++;
33         vct[s].push_back(s+l);
34     }
35     memset(f,-1,sizeof(f)); 
36 }
37 
38 int search(int i){
39     if(i>t) return 0;
40     else if(sch[i]==0){
41         if(f[i+1]!=-1) return f[i]=f[i+1]+1;
42         return f[i]=search(i+1)+1;
43     }else if(sch[i]!=0){
44         int maxv=0;
45         for(int j=0;j<sch[i];j++){
46             //这句话有问题 ,只记忆化上面的不记忆化这部分,只有6号点一个点超时 
47             //if(f[vct[i][j]]!=-1) return f[i]=f[vct[i][j]];
48             //这样就都过了 
49             int tmp;
50             if(f[vct[i][j]]!=-1) tmp=f[vct[i][j]];
51             else tmp=search(vct[i][j]);
52             if(tmp>maxv) maxv=tmp;
53         }
54         return f[i]=maxv;
55     }
56 }
57 
58 int main(){
59     //freopen("in.txt","r",stdin);
60     //freopen("testdata.in","r",stdin);
61     init();
62     cout<<search(1)<<endl; 
63     return 0;
64 }

1、肯定要由易到难,先写递归,再写记忆化递归

2、递归肯定要想清楚了再写,把递推公式想清楚

3、看上面的AC情况图,记忆化递推和DP的时间耗时是一样的

4、第46,47行,我们只部分记忆化的话,可以过9个点

5、memset(f,-1,sizeof(f)); 

 
 纯暴力模拟,30分 ,可以了 
 1 /*
 2 纯暴力模拟,30分 ,可以了 
 3 */
 4 #include <bits/stdc++.h>
 5 const int N=1e4+10;
 6 using namespace std;
 7 int n,k,f[N],num,ans=0;
 8 vector<int> vct[N];
 9 
10 void search(int s){
11     if(s>n){if(num>ans) ans=num; return ;}
12     if(vct[s].empty()){ num++,search(s+1);num--;}
13     else for(int i=0;i<vct[s].size();i++){
14         int l=vct[s][i];
15         search(s+l);
16     }
17 }
18 
19 int main(){
20     //freopen("in.txt","r",stdin);
21     cin>>n>>k;
22     for(int i=1;i<=k;i++){ int s,l; cin>>s>>l; vct[s].push_back(l);}
23     search(1);
24     cout<<ans<<endl;
25     return 0;
26 } 

 

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值