poj 1390 较难的区间dp

7月9日二更:突然发现一个神奇的事情,把代码写成这样会更快(110ms)(就是用临时变量ans代替f[l][r][elen]),可见全局变量的寻址时间还是比较长的

开O3优化,实际效果几乎等于0

int dp(int l, int r,int elen) {
	if (l == r)return (len[r] + elen) * (len[r] + elen);
	if (f[l][r][elen])return f[l][r][elen];

	int ans = dp(l, r - 1, 0) + (elen + len[r])*(elen + len[r]);
	for (int i = l; i <= r - 2; i++) 
		if (col[i] == col[r]) {
			ans = max(ans, dp(l, i, elen + len[r])
				+ dp(i + 1, r - 1, 0));
		}
		
	return f[l][r][elen] = ans;
}

网上的代码基本上都一样,都是从北大的算法课过来的。

题意:给你一个涂有颜色的方块的序列,每次你可以删除一些相同颜色并且相邻的颜色块,并获得删除数目平方的收益,现在给你一个颜色块序列,问收益最大值是多少?

Sample Input

2 //表示t
9 //n
1 2 2 2 2 3 3 3 1 //表示颜色
1
1
Sample Output

Case 1: 29
Case 2: 1

思路:
第一步先把方块合并成连续且颜色一样的大块,方便处理。

对于L到R区间的最优解,一般人可能会想,要么分成三截,先删除中间一节,然后期望两边的颜色一样,在删除两边的;或者直接从中间切成两截,然后求值。比较两种情况的最大值。

其实这种思路是有问题的,比如1 2 1 1 1 2 1 ,你可以分成五截,1 3 5截颜色一样,你可以先删除2 4 截,然后1 3 5 一起删,明显比上一种想法更优。

但你不可能枚举每中分截情况,所以你可以考虑先把右边的留着不删,假设dp(l,r,e)表示l,r区间的最大值,且之前还有e个方块没删,e的颜色和第r个方块颜色一样。

你有两种方案,第一种是把r和e一起删了,然后去计算dp(l,r-1,0);第二种是中间有个方块k和r同色,那么期望k和r+e合并在删,就是dp(l, k, e + len[r])+ dp(k+ 1, r - 1, 0)。dp(l, k, e + len[r])表示l到k区间的最优解,希望e+len[r]+len[k]可以继续往下合并,dp(k+ 1, r - 1, 0)就是k+1到r-1的最优解。

#include <cstdio>
#include <iostream>
#include <iomanip>
#include <string>
#include <cstdlib>
#include <cstring>
#include <queue>
#include <set>
#include <vector>
#include <map>
#include <algorithm>
#include <cmath>
#include <stack>

#define INF 0x3f3f3f3f
#define IMAX 2147483646
#define LINF 0x3f3f3f3f3f3f3f3f
#define ll long long
#define ull unsigned long long
#define uint unsigned int

using namespace std;

int t, n, a[222],f[222][222][222],len[222],col[222],tot;

int dp(int l, int r,int elen) {
	if (l == r)return (len[r] + elen) * (len[r] + elen);
	if (f[l][r][elen])return f[l][r][elen];

	f[l][r][elen] = dp(l, r - 1, 0) + (elen + len[r])*(elen + len[r]);
	for (int i = l; i <= r - 2; i++) 
		if (col[i] == col[r]) {
			f[l][r][elen] = max(f[l][r][elen], dp(l, i, elen + len[r])
				+ dp(i + 1, r - 1, 0));
		}
		
	return f[l][r][elen];
}

int main() {
	scanf("%d", &t);
	for (int c = 1; c <= t; c++) {
		scanf("%d", &n);
		tot = 0;
		for (int i = 1; i <= n; i++) {
			scanf("%d", a + i);
			if (i == 1 || a[i] != a[i - 1]) {
				len[++tot] = 1;
				col[tot] = a[i];
			}
			else
				len[tot]++;
		}
		
		memset(f, 0, sizeof(f));
		printf("Case %d: %d\n", c, dp(1, tot,0));
	}
	
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值