[dp] P2135 方块消除

posted on 2024-07-03 06:09:52 | under | source

挺好一道区间 dp。题解区写的都啥啊,根本看不懂。

首先假如相邻颜色段颜色相等,我们就将其合并,记颜色为 \(a_i\),数量为 \(b_i\)。可以证明,我们一定不会把一个颜色段拆开来操作,因为价值是平方,有 \((x+y)^2\ge x^2+y^2\)

容易想到定义 \(g_{i,j}\) 表示消除 \([i,j]\) 内的所有颜色段的最大价值。转移就是枚举段内颜色,将所有该颜色的单独拎出来最后一起操作,然后价值就是其余段的 \(g\) 之和以及该颜色数量的平方。

但这是错误的!因为不一定要把所有该颜色的都放在一起最后消掉,我们可以选取一部分让它先被消掉以满足某些需求。

例如:

5
1 2 1 2 1
4 6 1 6 4

可以先消中间那段 1,使得两段 2 合并,最后合并两端的 1。这样更优。

如何解决?不妨将所有选取方案(即选择放在最后消掉的元素)分为两类:\(r\) 被选和不被选。假如 \(r\) 不被选,那么正常进行区间合并即可枚举到所有情况。假如 \(r\) 被选,必须记录当前被选元素的总数(\(\sum b\))。

\(r\) 是区间右端点。不妨记 \(f_{i,j,k}\)\([i,j]\) 中,选取元素颜色为 \(a_j\)\(j\) 一定被选取,选取元素数量之和为 \(k\) 时,的最大价值(注意先不计算选取元素的价值)。那么选 \(r\) 方案最大值就是 \(\max\limits_k f_{i,j,k}+k^2\to g_{i,j}\)

转移很 simple,枚举 \([i,j-1]\)\(a_p=a_j\)\(p\),再枚举 \(k\),那么:\(f_{i,p,k}+g_{p+1,j-1}\to f_{i,j,k+b_j}\)

\(m=\sum b\),复杂度 \(O(n^3m)\),瓶颈在 \(f\) 转移上。注意卡好枚举上下界,轻松跑过。

代码

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

#define int long long
const int N = 55, C = 1e3 + 5;
int _n, n, a[N], b[N], f[N][N][C], g[N][N], sm[N][N]; 

signed main(){
	memset(f, -1, sizeof f); //不要出现非法情况 
	cin >> _n;
	for(int i = 1; i <= _n; ++i) scanf("%lld", &a[i]);
	for(int i = 1; i <= _n; ++i) scanf("%lld", &b[i]);
	for(int i = 1; i <= _n; ++i) //去重 
		if(a[i] == a[n]) b[n] += b[i];
		else a[++n] = a[i], b[n] = b[i];
	for(int i = 1; i <= n; ++i){		
		g[i][i] = b[i] * b[i], f[i][i][b[i]] = 0;
		for(int j = 1; j <= _n; ++j) sm[i][j] = sm[i - 1][j] + (a[i] == j ? b[i] : 0); //下面转移的循环上界 
	}
	for(int len = 2; len <= n; ++len)
		for(int i = 1; i + len - 1 <= n; ++i){
			int j = i + len - 1;
			f[i][j][b[j]] = g[i][j - 1]; //假如只有j被选取 
			for(int k = i; k < j; ++k){
				g[i][j] = max(g[i][j], g[i][k] + g[k + 1][j]);
				if(a[k] == a[j])
					for(int p = b[k]; p <= sm[k][a[k]] - sm[i - 1][a[k]]; ++p)
						if(f[i][k][p] != -1)
							f[i][j][p + b[j]] = max(f[i][j][p + b[j]], f[i][k][p] + g[k + 1][j - 1]);
			}
			for(int p = b[j]; p <= sm[j][a[j]] - sm[i - 1][a[j]]; ++p) 
				if(f[i][j][p] != -1) g[i][j] = max(g[i][j], f[i][j][p] + p * p);
		}
	cout << g[1][n];
	return 0;
}
/*
5
1 2 1 2 1
4 6 1 6 4
209
*/
posted @ 2026-01-12 19:59  Zwi  阅读(2)  评论(0)    收藏  举报