1. 程式人生 > 實用技巧 >UVA10559 方塊消除【費用提前計算】

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;
}