题解:qoj9698 Twenty-two

题意:给出一个数列 \(a\),再给出若干次操作,每次操作形如以下两种:

  1. 所有数对 \(v\) 取 min。

  2. 区间对 \(v\) 取 max。

求最后的序列有多少种。

做法:

首先我们注意到一件事情,我们的原数组是不影响答案的,因为一个数要不然按自己算,要不然就是被取 max 后又取 min,或者你可以认为每个操作其实可以做无限次,但是其实有效的还是最后的,所以是不会影响最后的计数结果的,我们可以先认为所有数都是 0。

那么我们就考虑先把取 min 操作从小往大排,因为大的如果在小的前面完全没用,并且这么做也确实可以覆盖所有的操作方式,那么现在就等于我要把 max 操作插进去,每个数就变成了先对其取 max, 然后最后被取一次 min 状物的取值。

那么我们这里就可以开始考虑 dp 了,因为我们的更新都是一段段区间,所以我们考虑区间 dp,\(dp_{l,r,x}\) 代表我现在有的区间为 \([l,r]\),并且其中每个数都 \(\ge x\)

如何转移呢?我们考虑先按值域扫描。考虑对于 \(\ge x+1\)\(\ge x\) 如何转移,首先我们要判断一个区间能否被置成 \(x\),如果一个 max 区间能变成 x 是可以的,还有一种是我们通过全局取 min 做到的。

但是直接去通过枚举我们的区间去转移是困难的,因为你的区间有交集的情况相当难处理,我们考虑正难则反,考虑去处理有哪些位置不能有减掉。

首先考虑处理总的情况,我们直接认为目前怎么填都能有 \(x\),那么我们就等于要把 \(\ge x+1\) 的若干个区间拼在一起成为一个大的 \(\ge x\) 的区间即可,这个直接考虑最右端选的区间即可。

然后考虑如何减掉,我们先枚举左端点,讨论每一个更新操作,如果说我们有一个取 min 操作刚好为 \(x\),并且有一个 max 操作 \(\ge x\),那么我们就可以拉成 \(x\),如果没有取 min,但是刚好有一个 \(\max = x\) 的操作也是可行的,我们统计一下对于一个右端点,仅用这个右端点结束的区间最左到哪里。

然后我们再枚举区间的右端点,直接减会相当麻烦很容易重复,所以我们还是考虑枚举第一个合法区间,并且因为我们从小往大枚举右端点,在枚举的时候前面的贡献已经计算完了。

当我们扫到一个右端点的时候,我们将能满足 \(=x\) 的左端点到这个右端点之间全部打一个能被 cover 的标记,然后扫一遍在不能 cover 的地方减去即可,减去的代价即为左区间 \(\ge x\),右区间 \(\ge x\) 但是不一定满足的方案数。

给出代码,参考了 hos_lyric 的写法,使用左闭右开。

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 155, mod = 998244353;
int n, m, k, a[maxn], c[maxn], use[maxn], f[maxn][maxn], g[maxn][maxn];
int l[maxn], r[maxn], v[maxn], lx[maxn], cov[maxn];
signed main() {
	cin >> n >> m >> k;
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	for (int i = 1; i <= m; i++)
		cin >> c[i], use[c[i]] = 1;
	sort(c + 1, c + m + 1);
	for (int i = 1; i <= k; i++)
		cin >> l[i] >> r[i] >> v[i];
	for (int i = 0; i <= n; i++)
		f[i][i + 1] = 1;
	for (int i = n; i >= c[1]; i--) {
		memset(g, 0, sizeof(g));
		for (int l = n + 1; l >= 0; l--) {
			g[l][l] = 1;
			for (int r = l + 1; r <= n + 1; r++)
				for (int t = l; t < r; t++)
					g[l][r] = (g[l][r] + g[l][t] * f[t][r]) % mod;
		}
		memset(f, 0, sizeof(f));
		for (int l1 = n; l1 >= 0; l1--) {
			memset(lx, 0x3f, sizeof(lx));
			for (int j = 1; j <= k; j++) {
				if((use[i] && v[j] >= i) || v[j] == i) {
					if(l[j] > l1)
						lx[r[j]] = min(lx[r[j]], l[j]);
				}
			}
			memset(cov, 0, sizeof(cov));
			for (int r1 = l1 + 1; r1 <= n + 1; r1++) {
				for (int t = lx[r1 - 1]; t < r1; t++)
					cov[t] = 1;
				f[l1][r1] = (f[l1][r1] + g[l1][r1]) % mod;
				for (int t = l1 + 1; t < r1; t++)
					if(!cov[t])
						f[l1][r1] = (f[l1][r1] - f[l1][t] * g[t][r1] % mod + mod) % mod;
			}
		}
	}
	cout << g[0][n + 1] << endl;
	return 0;
}
posted @ 2025-08-11 08:33  LUlululu1616  阅读(23)  评论(0)    收藏  举报