P10600 BZOJ4350 括号序列再战猪猪侠 Solution

你是图论大神,首先拓扑排序一遍给定的限制,如果有环直接输出 \(0\)

你是 bitset 大神,考虑拓扑排序出一个布尔数组 \(F_{i,j}\),表示 \(match_j\) 在限制中是否小于 \(match_i\)。然后再或一下得到一个数组 \(s_{l,r,k}\) 表示 \(l\)\(r\) 中是否存在任意一个满足其 \(match\) 值大于 \(match_k\),也就是 \(F\)\(l\) 按位或到 \(r\)

你是 dp 大神,定义状态 \(f_{l,r}\) 表示第 \(l\) 个到第 \(r\) 个左括号及其右括号填满的方案数。你发现有两种转移:

  • 形如 (S),也就是左端点包住整个区间,仅当 \(match_l\) 可以大于 \(match_{l+1},match_{l+2},\cdots,match_{r}\),也就是 \(s_{l+1,r,l}=0\) 时可以转移,\(f_{l,r}\leftarrow f_{l+1,r}\)
  • 形如 ST,也就是拆分成两个并列的区间,我们设 \(k\) 为拆分出来的左区间的右端点,当且仅当没有限制使得右区间的 \(match\) 会小于左边,也就是 \(s_{l,k}\)\([k+1,r]\) 这一段都为 \(0\),可以通过对 \(s\) 前缀和简单判断。\(f_{l,r}\leftarrow f_{l,k}\times f_{k+1,r}\)

然后你轻松码完代码,发现样例都过不了。第三组数据你输出 \(2\),而非 \(1\)。你发现对于 ()()(),你在处理 \(f_{1,3}\) 时,在 \(k=1,2\) 的时候都会做贡献,但是是重复的贡献,然后倒闭。所以你决定修改方程。

为了避免重复贡献,我们考虑钦定左端点包住左区间,也就是:

  • 形如 (S)T,在上文第二种转移的基础上,我们保证 \(l\) 能包住 \([l+1,k]\),也就是 \(match_l\) 可以大于 \(match_{l+1}\)\(match_k\) 的所有,转移:\(f_{l,r}\leftarrow f_{l+1,k}\times f_{k+1,r}\)
  • 这时候,你睿智地发现,上文第一种转移其实就等于 \(k=r\) 的时候转移,于是可以合并两部分的代码。

然后你就通过了。

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 310;
const LL MOD = 998244353;
int n, m, in[N]; LL DP[N][N];
vector<int> G[N]; bitset<N> f[N], ff[N][N]; int pre[N][N][N];

LL DFS(int l, int r) {
	if (l >= r) return 1; // 因为 k 可能为 r,导致区间 r+1,r 产生贡献,所以钦定 l>r 也为 1
	if (DP[l][r] != -1) return DP[l][r];
	LL ret = 0, pt = l;
	while (pt < r && !f[pt + 1][l]) ++ pt; // 找到 l 最远能包住的点
	for (int k = l; k <= pt; k ++) if (pre[l][k][r] == pre[l][k][k]) {
		ret = (ret + DFS(l + 1, k) * DFS(k + 1, r)) % MOD;  // 转移
	} return DP[l][r] = ret;
}

int main() {
	freopen(".in", "r", stdin); freopen(".out", "w", stdout);
	ios :: sync_with_stdio(0); cin.tie(0); cout.tie(0);
	int _; cin >> _;
	while (_ --) {
		cin >> n >> m; 
		for (int i = 1; i <= n; i ++) in[i] = 0, G[i].clear(), f[i].reset();
		for (int i = 1, u, v; i <= m; i ++) {
			cin >> u >> v; in[v] ++; G[u].emplace_back(v);
		} queue<int> q; int cnt = 0;
		for (int i = 1; i <= n; i ++) if (!in[i]) q.push(i);
		while (!q.empty()) {
			++ cnt; int u = q.front(); q.pop();
			for (int v : G[u]) {
				f[v].set(u); f[v] |= f[u]; in[v] --; if (!in[v]) q.push(v);
			}
		} if (cnt < n) { cout << "0\n"; continue; }
		for (int i = 1; i <= n; i ++) {
			ff[i][i] = f[i];
			for (int j = i + 1; j <= n; j ++) ff[i][j] = ff[i][j - 1] | f[j];
		} // 也就是上文的 s 数组。
		for (int i = 1; i <= n; i ++) for (int j = i; j <= n; j ++) {
			for (int k = 1; k <= n; k ++) pre[i][j][k] = pre[i][j][k - 1] + ff[i][j][k];
		} // 前缀和便于判断。
		for (int i = 1; i <= n; i ++) for (int j = 1; j <= n; j ++) DP[i][j] = -1;
		cout << DFS(1, n) << "\n";
	}
	return 0;
}
posted @ 2025-02-09 10:12  LaDeX  阅读(26)  评论(0)    收藏  举报