『动态规划·奇葩状态设计』消木块 Blocks

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Ronaldo7_ZYB/article/details/91632990

题目描述

在这里插入图片描述在这里插入图片描述

题解

显然是一个区间DP,最直观的思路就是设置状态 f [ l ] [ r ] f[l][r] 为区间 [ l , r ] [l,r] 的最高得分。

但是对于中间消除以后再消边上操作会十分困难,我们这里采用一种费用提前计算的方法:

我们设 f [ l ] [ r ] [ k ] f[l][r][k] 表示在区间 [ l , r ] [l,r] 消完以后,还要消在 r r 右边颜色和 r r 一样的 k k 个的方案数。

在这里插入图片描述
我们可以用这幅图来诠释这个状态转移方程。

第一种情况,若 r r 不能与 r 1 r-1 合并,直接将 [ l , r 1 ] [l,r-1] 的贡献加上r和右边的k合并以后的贡献。即: f [ l ] [ r ] [ k ] = m a x ( f [ l ] [ r ] [ k ] , f [ l ] [ r 1 ] [ 0 ] + ( k + 1 ) 2 ) f[l][r][k]=max(f[l][r][k],f[l][r-1][0]+(k+1)^2)
第二种情况,将 [ 1 , r ] [1,r] 中的两边合并,合并以后再用 r r 和右边的某个 k k 合并。此时需要保证 [ 1 , r ] [1,r] 中合并的两端必须颜色相同。即: f [ l ] [ r ] [ k ] = m a x ( f [ l ] [ r ] [ k ] , f [ l ] [ i ] [ k + 1 ] + f [ i + 1 ] [ r 1 ] [ 0 ] ) f[l][r][k]=max(f[l][r][k],f[l][i][k+1]+f[i+1][r-1][0])
对于枚举i,我们需要枚举每一个和r颜色相同的点,我们可以额外用一个 p r e i pre_i 表示上一个和i颜色相同的点的位置。这样我们可以避免无用的枚举。

这道题的启示就是,我们当未来某一个状态还不确定的时候,适当的采用费用提前的手段,并增加一维状态来限制这一个答案。例如,这里的 k k 就是一个假想的存在,其作用就是用于费用提前计算来方便我们统计答案。

代码如下:

#include <bits/stdc++.h>

using namespace std;
const int N = 300;
int n;
int a[N], last[N], pre[N], f[N][N][N];

int dp(int l,int r,int k)
{
	if (l > r) return 0;
	if (f[l][r][k] > 0) return f[l][r][k];
	if (a[r-1] ^ a[r]) f[l][r][k] = dp(l,r-1,0)+(1+k)*(1+k);//格子r单独和右边的格子消 
	for (int i=pre[r];i>=l;i=pre[i])//枚举每一个和颜色r相同的格子 
	    f[l][r][k] = max(f[l][r][k],dp(l,i,k+1)+dp(i+1,r-1,0));//将i和「r和后面的k合并以后的格子」 合并 
	return f[l][r][k];
}

int main(void)
{
	freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);
	scanf("%d",&n);
	for (int i=1;i<=n;++i) {
		scanf("%d",a+i);
		pre[i] = last[a[i]];//记录「和位置i颜色相同的上一次出现」的格子的位置 
		last[a[i]] = i;
	}
	cout << dp(1,n,0) << endl;
	return 0; 
}

猜你喜欢

转载自blog.csdn.net/Ronaldo7_ZYB/article/details/91632990