下周就是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;
}
最后,分析一下我和大佬的区别,基本的出发点是相同的,但是我寻找到的状态转移太多了。好吧,太菜了,分析不出来啥东西。