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。
输出格式:
输出文件仅一行,包含一个整数,表示尼克可能获得的最大空暇时间。
输入输出样例
15 6 1 2 1 6 4 11 8 5 8 1 11 5
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 /* 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));
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 }