九度1499

这题与很久以前做过一个贪心题类似,给你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数组是单调不下降了,直接由这个状态转移即可。

你可能感兴趣的:(优化,dp,二分法)