1. 程式人生 > 其它 >【題解】UVA10559 方塊消除 Blocks

【題解】UVA10559 方塊消除 Blocks

【題解】UVA10559 方塊消除 Blocks

設計狀態

  • \(f(i,j)\) 表示合併 \(i\) 區間至 \(j\) 區間可得的最大分數

    但如果合併一段之後,前後兩段接在了一起,那麼接在一起的這段能產生的分數一定多於兩段分別消除所得分數(因為 \((a+b)^2\geq a^2+b^2\)

    那麼可以考慮向當前區間後面再接 \(k\) 個相同顏色的塊一起消除,那麼所產生的總分數是:接在一起後的區間消除所得分數+ 消除中間的雜色塊所得分數

  • 那麼狀態就變成了 \(f(i,j,k)\) ,表示合併 \(i\) 區間至 \(j\) 區間,並在 \(j\) 區間後補上與 \(j\) 顏色相同的 \(k\)

    個能獲得的最大分數

狀態轉移方程

為了方便,

\(\text{len}_i\) 表示第 \(i\) 段顏色相同且連續的區間的長度

對於一個區間 \([l,r]\),右邊有 \(k\) 個與 \(j\) 同色的方塊,我們可以

  1. \(r\) 和後面的 \(k\) 個方塊一起消掉, \(f(l,r,k)=f(l,r-1,0)+(\text{len}_r+k)^2\)
  2. \([l,r-1]\) 中尋找一個與 \(r\) 顏色相同的塊 \(p\) ,消除 \(p\)\(r\) 之間的所有方塊後 \(p\)\(r\) 相鄰可以一起消除,\(f(l,r,k)=f(p+1,r-1,0)+f(l,p,k+1)\)

實現細節

這裡在同一個在程式碼塊中的程式碼可能不在同一個縮排層級下

  • 因為連續的同色方塊一定是一起消除的,所以可以將一段連續的同色方塊看成一個方塊

    struct Block{
        int len,col;
    }block[maxN];
    
    int cnt = 0;
    block[0].col = -1;
    
    for(int i = 1;i<=N;i++){
        int col;
        scanf("%d",col);
        if(col == block[cnt].col) block[cnt].len++;
        else{
            block[++cnt].len = 1;
            block[cnt].col = col;
        }
    }
    
    N = cnt;
    
  • 因為這是一道區間dp,所以基本框架還是區間dp的框架:

    for(int len = 0;len <= N;len++){
        for(int l = 1;l + len <= N;l++){
            int r = l + len;
            ...
        }
    }
    
  • 對於情況1,我們可以預處理出來如果每個方塊後有 \(k\) 個同色方塊時可以獲得的分數

    \(k\) 最大為 \(\text{sum}_n-\text{sum}_{j-1}\)\(\text{sum}_i\) 表示前 \(i\) 段顏色相同的區間包含的方塊總數,可以預處理出來

    for(int i = 1;i<=N;i++) sum[i] = sum[i-1] + block[i].len;
    
    for(int k = 0;k <= sum[N]-sum[r-1];k++){
        dp[l][r][k] = dp[l][r-1][0] + pow2(block[r].len + k);
    }
    
  • 對於情況2,可以列舉 \(p\) 所在位置,然後去更新該區間的得分

    for(int p = l;p<r;p++){
        if(block[p].col != block[r].col) continue;
        for(int k = 0;k<=sum[N]-sum[j-1];k++){
            dp[l][r][k] = max(dp[l][r][k],dp[l][p][block[r].len+k]+dp[p+1][r-1][0])
        }
    }
    

最後的結果是 \(f(1,N,0)\)

完整程式碼

#include<bits/stdc++.h>
using namespace std;

const int maxN = 400;

struct Block{
	int len,col;
}block[maxN];

int dp[maxN][maxN][maxN];
int sum[maxN];

int N;
int cnt;

inline int pow2(int x){
	return x * x;
}

inline void INIT(){
	block[0].col = -1;
	cnt = 0;
	memset(sum,0,sizeof(sum));
	memset(dp,0,sizeof(dp));
}

inline void work(int Case){
	INIT();
	scanf("%d",&N);
	for(int i = 1;i<=N;i++){
		int col;
		scanf("%d",&col);
		if(col == block[cnt].col) block[cnt].len++;
		else{
			block[++cnt].len = 1;
			block[cnt].col = col;
		}
	}
	N = cnt;

	for(int i = 1;i<=N;i++) sum[i] = sum[i-1] + block[i].len;

	for(int len = 0;len<=N;len++){
		for(int l = 1;l + len <= N;l++){
			int r = l + len;
			for(int k = 0;k<=sum[N]-sum[r-1];k++){
				dp[l][r][k] = dp[l][r-1][0] + pow2(block[r].len + k);
			}
			for(int p = l;p<r;p++){
				if(block[p].col != block[r].col) continue;
				for(int k = 0;k<=sum[N]-sum[r-1];k++){
					dp[l][r][k] = max(dp[l][r][k],dp[l][p][block[r].len+k]+dp[p+1][r-1][0]);
				}
			}
		}
	}
	printf("Case %d: %d\n",Case,dp[1][N][0]);
}
int main(){
	int T;
	scanf("%d",&T);
	int Case = 1;
	while(Case <= T) work(Case++);
	return 0;
}