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

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

题目链接

解析

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

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

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

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

单独消除的状态是很好转移的:f[i][j]=max(f[i][j1]+bj2,f[i+1][j]+bi2)

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

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

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

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

为啥只需要记录j,而不用管[i,j1]呢,是因为:

假设[i,j1]中某点x未来会与p相连。

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

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

最后得到转移方程:

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

f[i][j][k]=f[i][x][k+1]+f[x+1][j1][0],i<=x<j,ax==ajx为前面一个和j颜色相同的位置,拿掉[x+1,j1],那么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 @   Starlight_Glimmer  阅读(161)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
浏览器标题切换
浏览器标题切换end
点击右上角即可分享
微信分享提示