HDU 6249 Alice's Stamps

下周就是CCPC-final了,和队友打了一下去年的final不出意外打铁了,菜的真实,两个小时多一点出了四题,第五题DP只会O(n^3)的写法,看了网上大佬们的写法才过了这个题目。

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6252

题意:

已知有N种邮票,M种套餐,每种套餐会包括编号为[l,r]的邮票,只能选择其中的K种套餐,问最多能收集到多少种邮票?
换个说法:
给出M个区间,范围都在[1,N],求选K个区间的区间并最大为多少?

题目分析:

对于这个题目,我一开始想到的dp[i][j]表示的是,目前选取的区间最右端为i,选取了j个区间的得到的最大区间并,对于每个区间枚举i。

//对于小于当前的区间的i直接加上当前区间长度,其他的就加上原来的区间最右点和当前区间最大点之间的差值
if(p[j].l > t)
      dp[i][p[j].r] = max(dp[i - 1][t] + p[j].r - p[j].l + 1,dp[i][p[j].r];
else
      dp[i][p[j].r] = max(dp[i - 1][t] + p[j].r - t,dp[i][p[j].r]);

结果发现我需要枚举一次j,然后枚举每个线段,然后再枚举i,是O(n^3)的复杂度,直接爆炸。
于是我看了网上大佬们的写法,发现我的状态方程真是菜的一批。
dp[i][j]代表1~i选取j段取得的区间最大长度
res代表包含当前判断的节点的区间右端点最大的区间的右端点位置

//对于当前节点来说可以考虑不用新的区间去覆盖
dp[i + 1][j] = max(dp[i + 1][j],dp[i][j]);
//对于当前节点去覆盖的选取最优的覆盖,也就是用res所代表的那个区间覆盖
dp[i + res][j + 1] = max(dp[i + res][j + 1],dp[i][j] + res);

所以这样我们就把复杂度由原来的复杂度由O(n ^ 3) 变到了O(n^2)
代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
#define MAXN 2003
using namespace std;

int dp[MAXN][MAXN];
struct node
{
    int l,r;
}p[MAXN];
//按照左端点排序,使得判断每个点的res值的时候不需要将所有的区间枚举一遍
bool cmp(node a,node b)
{
    return a.l < b.l;
}
int main()
{
    int T,cnt = 0;
    scanf("%d",&T);
    while(T--)
    {
        int m,n,k;
        scanf("%d%d%d",&n,&m,&k);
        for(int i = 1;i <= m;i++)
            scanf("%d%d",&p[i].l,&p[i].r);
        sort(p + 1,p + 1 + m,cmp);
        memset(dp,0,sizeof(dp));
        int pos = 1,res = 0;
        for(int i = 0;i < n;i++)
        {
            while(pos <= m && p[pos].l == i + 1 )
            {
                //printf("T%d %d\n",p[i].l,p[i].r);
                res = max(p[pos].r - p[pos].l + 1,res);
                pos++;
            }
            //printf("%d\n",res);
            for(int j = 0;j <= k;j++)
            {
                dp[i + 1][j] = max(dp[i + 1][j],dp[i][j]);
                dp[i + res][j + 1] = max(dp[i + res][j + 1],dp[i][j] + res);
            }
            if(res)
                res--;
            //对于下一个点来说,当前节点的res值减去1可以作为下一个点的res的初始值
        }
        /*
        for(int i = 1;i <= n;i++,printf("\n"))
            for(int j = 1;j <= k;j++)
                printf("%d ",dp[i][j]);
        */
        printf("Case #%d: %d\n",++cnt,dp[n][k]);
    }
    return 0;
}

最后,分析一下我和大佬的区别,基本的出发点是相同的,但是我寻找到的状态转移太多了。好吧,太菜了,分析不出来啥东西。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值