[dp] P2135 方块消除

· · 题解

挺好一道区间 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)。

转移很 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$ 转移上。注意卡好枚举上下界,轻松跑过。 #### 代码 ```cpp #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 */ ```