你是图论大神,首先拓扑排序一遍给定的限制,如果有环直接输出 \(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 也为 1if (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;
}