NYOJ 720 项目安排(DP)

题目链接:Click here~~

题意:

给出 n 个区间 [a,b],每个区间有个权值 Wi,如何取不相交的区间,使权值最大。

解题思路:

估计是一道很经典的题目,目前学习了三种解法。

1、如果区间范围比较小,例如 ZOJ 3637。则可以用 dp[i] 表示考虑到数轴中 i 点时的最大值。

存区间的时候,不再存入结构体,而是存入一个下标为起始点值的vector。当考虑到 i 点时,dp[i] 一定是最大值,然后用 dp[i] 去向后更新 dp[j]。

复杂度O(m+n)。(m 是区间总范围)

#include <vector>
#include <string>
#include <stdio.h>
#include <string.h>
#include <algorithm>

using namespace std;

const int N = 24*60*370 + 10;

int get_month(string s)
{
    if(s == "Jan")  return 1;
    if(s == "Feb")  return 2;
    if(s == "Mar")  return 3;
    if(s == "Apr")  return 4;
    if(s == "May")  return 5;
    if(s == "Jun")  return 6;
    if(s == "Jul")  return 7;
    if(s == "Aug")  return 8;
    if(s == "Sep")  return 9;
    if(s == "Oct")  return 10;
    if(s == "Nov")  return 11;
    if(s == "Dec")  return 12;
}

int day[] = {0,31,60,91,121,152,182,213,244,274,305,335,366};

int read()
{
    char str[5];
    int mon,d,h,m;
    scanf("%s",str);
    mon = get_month(str);
    scanf("%d%*s",&d);
    scanf("%d:%d",&h,&m);
    scanf("%s",str);
    if(str[0] == 'p')
        h += 12;
    return m + h * 60 + (d + day[mon-1]) * 24 * 60;
}

vector< pair<int,double> > events[N];

double dp[N];

int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        for(int i=0;i<N;i++)
            events[i].clear();
        for(int i=0;i<n;i++)
        {
            int t1 = read();
            int t2 = read() + 5;
            double val;
            scanf("%lf",&val);
            events[t1].push_back(make_pair(t2,val));
        }
        dp[0] = 0;
        for(int i=0;i<N;i++){
            dp[i+1] = max(dp[i+1],dp[i]);
            for(int j=0;j<(int)events[i].size();j++){
                int t = events[i][j].first;
                dp[t] = max(dp[t],dp[i]+events[i][j].second);
            }
        }
        printf("%.1f\n",dp[N-1]);
    }
    return 0;
}

2、如果区间比较大,就只能先按照区间右端点排序,然后有两种状态表示。

1> dp[i] 记录选第 i 个区间时的最大值,然后 dp[i] = max{ dp[j] + w }(j < i &&  i,j 不相交)。其实是这样。这是上次网络赛自己想的。复杂度O(n*n)。

2> dp[i] 记录考虑前 i 个区间的最大值,然后 dp[i] = max(dp[i-1] , dp[j] + w)(j < i && i,j 不相交)。也就是这题的做法了。

其实 2> 应该叫做 1> 的升级版。因为转移的时候,第一个是一个集合,而第二个是一个值(只要找到最大的这样的 j 就行了,因为 dp[i] >= dp[i-1])。

而这个特性,还可以通过二分,使转移的复杂度下降到O(log n),从而总复杂度 O(n*log n)。

#include <stdio.h>
#include <algorithm>

using namespace std;

const int N = 5e3 + 5;

struct Int{
    int a,b,w;
    bool operator < (const Int& S) const{
        return b < S.b || b == S.b && a < S.a;
    }
}A[N];

int find(int j)
{
    int l = 1 , r = j;
    while(l < r)
    {
        int mid = (l + r) / 2;
        if(A[mid].b <= A[j].a)
            l = mid + 1;
        else
            r = mid;
    }
    return r - 1;
}

int dp[N];

int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        for(int i=1;i<=n;i++)
            scanf("%d%d%d",&A[i].a,&A[i].b,&A[i].w);
        sort(A+1,A+1+n);
        dp[0] = 0;
        for(int i=1;i<=n;i++){
            dp[i] = max(dp[i-1],dp[find(i)]+A[i].w);
        }
        printf("%d\n",dp[n]);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值