题解: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}\) 表示之前至少操作几次,分类转移:

  1. \(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}\)

  2. 内部拼接:\(\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;
}
posted @ 2026-01-15 08:18  Zwi  阅读(2)  评论(0)    收藏  举报