[dp] P2135 方块消除
_Cheems
·
·
题解
挺好一道区间 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
*/
```