把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

UVA10559 方块消除【费用提前计算】

题目链接

解析

显然这道题重点在于消掉一些块之后会产生一些新的连续的块可以一起消,这个不能想到别的什么做法,可以考虑区间\(dp\)

不过对于一整坨,你要么把他们全部一起消掉,要么就留着等更多的过来一起消掉,你总不可能把一坨分批消掉吧,明显\((x+y)^2>=x^2+y^2\)

所以刚开始可以把相同颜色的一段块看成一点(不过我太懒了,最后没有这么写,当然,这不是重点,用\((a,b)\)表示有\(b\)个颜色为\(a\)的方块相连。

用惯常的思路进行考虑,设\(f[i][j]\)表示消除区间\([i,j]\)的得分。

单独消除的状态是很好转移的:\(f[i][j]=max(f[i][j-1]+b_j^2,f[i+1][j]+b_i^2)\)

但如果是保留这一段,然后等着和其它的方块一起消呢?我们发现这个状态难以转移,因为这个区间的得分还与这个区间以外的前面消方块的状态息息相关。如果记录下前面消了哪些,还有哪些,在什么位置之类的相关信息,状态数无疑是巨大的。

类比于之前小球的做法,在之前预先计算得分,我们发现这个似乎也行不通,因为这个得分不再是简单的线性关系,而是二次函数,过去的贡献与现在消去的长度有关。

返回去想到这道题重点在于消掉一些块之后会产生一些新的连续的块可以一起消,对于保留的情况,我们是把\(j\)留着和之前剩下的一串同色的方块合在一起消,如果这些合并在之前被料到并预先计算了得分,并通过状态转移到现在,那么就可以转移了。

对于状态\(f[i][j][k]\)考虑假设在未来经过一些消除操作后,有\(k\)个与\(j\)同色的方块在右边和\(j\)拼在了一起,的最大得分。

为啥只需要记录\(j\),而不用管\([i,j-1]\)呢,是因为:

假设\([i,j-1]\)中某点\(x\)未来会与\(p\)相连。

  1. \(p\)的位置在\(k\)左边,即\(x<j<p<k\),那是不可能的,因为未来\(j\)会与\(k\)相连,而\(p\)挡道了,所以\(p\)一定是在\(j,k\)之间消掉的,没机会往\([i,j-1]\)那个区间接触。
  2. \(p\)的位置在\(k\)右边,即\(x<j<k<p\),那么这个状态是个包含关系,\(x\)\(p\)产生的贡献可以通过\(f[x][p]\)计算,不用这个时候来操心。

所以:对于状态\(f[i][j][k]\),无论经过什么操作,\(i\)\(j-1\)之间只能与\(i\)\(j\)之间的区域相连,而区域\(j\)可以与\(j\)之后的的区域相连。

最后得到转移方程:

\(f[i][j][k]=f[i][j-1][0]+(k+1)^2\)\(j\)和它后面的\(k\)同系物同色块相消)

\(f[i][j][k]=f[i][x][k+1]+f[x+1][j-1][0],i<=x<j,a_x==a_j\)\(x\)为前面一个和\(j\)颜色相同的位置,拿掉\([x+1,j-1]\),那么\(j\)和它右边的小伙伴们和\(x\)拼成了一个更大的连续一串(这里就是上文说到的“在未来经过一些消除操作后”的“一些操作”))

直接\(dp\)有点难做,顺序有点乱,所以写成了记搜的样子。

最后答案是\(f[1][n][0]\)

(好久没写这么长的题解了,好家伙,写了一节课)

参考:《对一类动态规划问题的研究》 徐源盛


►Code View

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define N 205
#define INF 0x3f3f3f3f
#define LL long long
int rd()
{
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	return f*x;
}
int n,a[N],pre[N],pos[N];
LL f[N][N][N];//f[i][j][k]表示消去[i,j]区间 且j右边还有k个和j颜色相同的块
/*
主体部分是连续的[i,j]区间 没有动过
然后j右边有颜色相同的k块紧接在j后面 他们之间的方块已经消完 
*/
void Init()
{
	memset(f,0,sizeof(f));
	memset(pre,0,sizeof(pre));
	memset(pos,0,sizeof(pos));
}
LL dp(int i,int j,int k)
{
	if(i>j) return 0;
	if(f[i][j][k]) return f[i][j][k];
	LL res=dp(i,j-1,0)+(k+1)*(k+1);//和右边一整坨一起消掉
	int l=pre[j];
	while(l>=i)
	{
		res=max(res,dp(i,l,k+1)+dp(l+1,j-1,0));//拿掉[l+1,j-1] 相同颜色的又拼到一起了 
		l=pre[l];
	}
	return f[i][j][k]=res; 
}
int main()
{
	int T=rd();
	for(int cas=1;cas<=T;cas++)
	{
		Init();
		n=rd();
		for(int i=1;i<=n;i++)
		{
			a[i]=rd();
			pre[i]=pos[a[i]],pos[a[i]]=i;
		}
		printf("Case %d: %lld\n",cas,dp(1,n,0));
	} 
	return 0;
}
posted @ 2020-11-22 21:44  Starlight_Glimmer  阅读(158)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end