题解:CF1987F2 Interesting Problem (Hard Version)
posted on 2024-07-23 10:44:47 | under | source
考虑两个区间 \([l,r],[l_2,r_2]\),假如有 \(r<l_2\),那么对 \([l_2,r_2]\) 的操作必然不会影响 \([l,r]\)。
所以假如 \([L,R]\) 的某些操作需要之前若干次操作才能进行,不妨设其最大值为 \(k\),那么只要 \(L\) 之前的操作次数不小于 \(k\) 即可,我们可以前后交替操作。
所以只关心之前操作几次,显然之前操作次数越多越好,也就是从左到右操作会更优。
另外重要的一点是,我们选取的数对间一定不存在交叉,类比括号匹配。
综上,我们可以得到 dp 设计思路:先预处理出要将一个区间完全消掉的话,此前至少要操作多少次;然后线性 dp 扫一次即可。
具体而言,令 \(f_{i,j}\) 表示之前至少操作几次,分类转移:
-
\(a_i,a_j\) 匹配,前提是消掉 \(a_i\) 的次数不能少于消掉 \([i+1,j-1]\) 的次数:\(\max (f_{i+1,j-1},\frac{i-a_i}{2})\to f_{i,j}\)。
-
内部拼接:\(\max(f_{i,k},f_{k+1,j}-\frac{k-i+1}2)\to f_{i,j}\)。
线性 dp 是容易的,留给你自己实现。
复杂度 \(O(n^3)\)。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 8e2 + 5;
int T, n, a[N], f[N][N], g[N], inf;
signed main(){
cin >> T;
while(T--){
scanf("%lld", &n);
memset(f, 0x3f, sizeof f), memset(g, 0, sizeof g);
for(int i = 1; i <= n; ++i) scanf("%lld", &a[i]), f[i][i - 1] = 0;
f[n + 1][n] = 0;
for(int len = 2; len <= n; len += 2)
for(int l = 1; l + len - 1 <= n; ++l){
int r = l + len - 1;
if(l - a[l] >= 0 && (l - a[l]) % 2 == 0 && (l - a[l]) / 2 >= f[l + 1][r - 1])
f[l][r] = max(f[l + 1][r - 1], (l - a[l]) / 2);
for(int k = l; k < r; ++k) f[l][r] = min(f[l][r], max(f[l][k], f[k + 1][r] - (k - l + 1) / 2));
}
for(int i = 1; i <= n; ++i){
g[i] = max(g[i], g[i - 1]);
for(int j = 1; j <= i; ++j){
if(g[j - 1] >= f[j][i])
g[i] = max(g[i], g[j - 1] + (i - j + 1) / 2);
}
}
printf("%lld\n", g[n]);
}
return 0;
}

浙公网安备 33010602011771号