这题与很久以前做过一个贪心题类似,给你n个任务,每个任务都有起始时间和结束时间,而且每个任务还有一个价值,不可以在同一时间同时做两个任务,也就是说不能一个任务没做完去做下一个任务,说白了就是给你一段区间,然后几个线段让你去放到区间上,区间和区间不能有重叠(边界有交集可以),每个区间有个价值,问你怎么放才能取得最大的价值。
我之前做的是价值为1的,或者说是问你最多可以安排多少个任务,这道题每个区间都是有价值的。这题一开始用了O(n^2)的方法,居然过了,这里定义dp[i]为第i个任务做完做多能获得最多的价值,注意一开始要按照结束时间排序,这个非常重要,然后状态转移方程是:
dp[i]=dp[j]+task[i].val (j<i &&task[i].st>=task[j].end)
括号里的是状态转移条件,最后的答案并不是dp[n],而是max(dp[i]),这点应该注意。代码如下:
#include<stdio.h> #include<algorithm> #include<string.h> #include<iostream> using namespace std; struct node { int st,end,va; }p[10001]; int dp[10001]; bool cmp(node a,node b) { return a.end<b.end; } int max(int x,int y) { return x>y?x:y; } int main() { int n; while(scanf("%d",&n)!=EOF) { int i,j; for(i=1;i<=n;i++) scanf("%d%d%d",&p[i].st,&p[i].end,&p[i].va); sort(p+1,p+n+1,cmp); int ans=0; memset(dp,0,sizeof(dp)); dp[1]=p[1].va; for(i=2;i<=n;i++) { dp[i]=p[i].va; for(j=i-1;j>=1;j--) { if(p[i].st>=p[j].end) dp[i]=max(dp[j]+p[i].va,dp[i]); } if(dp[i]>ans) ans=dp[i]; } printf("%d\n",ans); } }提交后我是590ms,然后我发现还有人居然只用了80ms,心想肯定有更好的方法状找状态转移方程,应该不需要找n次,或者说logn次?
由n变成logn一般的想法就是二分了,我代码里的p[i].end确实符合单调不下降,但是dp[i]并没有单调的,假如我能找到最大的j满足条件p[j].end<=p[i].st,我还是需要从j开始往前找最优状态,复杂度没有得到多少提高,那么假如dp[i]也是单调不下降的呢?假如我的dp[i]定义为,前i个任务能得到的最大价值,那么它显然是单调不下降的,注意这里定义方程和前面的区别,还有就是是否我每求出一个dp[i],我都与前面的i-1的dp数组元素比较呢,dp[i]既然保存了前i个任务获得最大值,那么dp[i-1]保存了前i-1个的最大值,我每次二分求得dp[i]后,只需要与dp[i-1]比较取一个较大值即可。如果我现在能把这个dp数组全算出来,那么答案肯定就是dp[n]了,所以有了如下代码:
#include<stdio.h> #include<algorithm> #include<string.h> #include<iostream> using namespace std; struct node { int st,end,va; }p[10001]; int dp[10001]; bool cmp(node a,node b) { return a.end<b.end; } int max(int x,int y) { return x>y?x:y; } int find(int l,int r,int k) //找个最后一个小于等于K { while(l<r) { int mid=(l+r)>>1; if(p[mid].end>k) r=mid; else if(p[mid].end<=k) l=mid+1; } return l-1; } int main() { int n; while(scanf("%d",&n)!=EOF) { int i,j; for(i=1;i<=n;i++) scanf("%d%d%d",&p[i].st,&p[i].end,&p[i].va); sort(p+1,p+n+1,cmp); memset(dp,0,sizeof(dp)); dp[1]=p[1].va; for(i=2;i<=n;i++) { dp[i]=p[i].va; int tmp=find(1,i,p[i].st); dp[i]=max(dp[tmp]+p[i].va,dp[i]); dp[i]=max(dp[i-1],dp[i]); } printf("%d\n",dp[n]); } }找最后一个小于等于某数的方法我在前面一篇二分想法解决8个问题中提到过,对于dp[i],我每次找到最后一个合p[j].end<=p[i].st条件的j(代码里是tmp),我不需要再往前找了,因为dp数组是单调不下降了,直接由这个状态转移即可。