[Coci2011]kamion 题解
前言
题目链接:Hydro & bzoj;黑暗爆炸。
题意简述
给你一张 \(n\) 个点 \(m\) 条边的有向图。有 \(p\) 种括号,每条边的边权可以是这 \(p\) 种括号中某一种的左括号或者右括号,也可以为空。问你有多少条从 \(1\) 开始到 \(n\) 的长度小于等于 \(k\) 的路径,满足括号匹配,或者剩余若干未配对的左括号。答案对 \(10007\) 取模。
\(2 \leq n \leq 50\),\(1 \leq m \leq n(n-1)\),\(1 \leq k \leq 50\),\(p = 26\)。
题目分析
一眼 DP。考虑状态,显然不可以把栈压到状态里,那我们干脆不记栈了,设 \(f_{i, u, v}\) 表示 \(u\) 到 \(v\),经过了恰好 \(i\) 条边,且此时栈为空的方案数。但是答案可以剩余左括号,所以不妨再记 \(h_{i, u, v}\) 表示栈中剩余若干未匹配的左括号,并且这些括号会一直留到最后。
这看起来十分正确,我们可以枚举中转点 \(w\),再枚举 \(u \rightarrow w\) 和 \(w \rightarrow v\) 分别经过了多少条边,两个子问题方案数累乘后做一遍累加即可:\(f_{i, u, v} = \sum \limits _ {w = 1} ^ n \sum \limits _ {t = 1} ^ {i - 1} f_{t, u, w} f_{i - t, w, v}\)。而 \(h\) 只不过是多了一种无用左括号的转移:\(g_{i, u, v} = \sum \limits _ {w \in \operatorname{out}(u)} g_{i - 1, w, v}\)。
事实上,这种做法存在「重复统计」的问题。具体来讲,如果一条路径形如 \(u {\tt ()()()} v\),会被 \(u {\color{red} \tt ()} w {\color{magenta} \tt ()()} v\)、\(u {\color{red} \tt ()()} w {\color{magenta} \tt ()} v\) 统计到。怎么解决呢?
事实上,回顾我们序列上的括号匹配问题,即「区间 DP」,同样存在这种重复统计。那我们是怎么去除呢?发现,只要保证转移的时候,是「第一次」进行某一个操作,例如,「第一次」放置一个矩形([CEOI2009] photo),就能够保证每一种情况只会被统计到一次(因为「第一次」是唯一的)。括号匹配的「第一次」便是「第一次」成为一个匹配的括号串。
区间上如此,图上亦如此。我们考虑枚举的中转点是「第一次」成为一个匹配的括号串的位置,也即,保证 \(u \rightarrow w\) 的路径两端是一对匹配上的括号(\(u\ \texttt{(...)}\ v\))。我们可以记一个 \(g_{i, u, v}\),和 \(f\) 定义类似,只不过保证 \(u \rightarrow v\) 的路径两端是一对匹配上的括号。这样,我们的 \(f\) 就能够通过 \(g\) 和 \(f\) 的路径拼接而来:\(f_{i, u, v} = \sum \limits _ {w = 1} ^ n \sum \limits _ {t = 1} ^ {\color{red} \mathbf{i}} g_{t, u, w} f_{i - t, w, v}\)。注意,此时 \(t\) 的上界是 \(i\),因为对于 \(u\ \texttt{(...)}\ v\) 本身就合法。当然,\(h\) 同样如此。
我们把目光聚焦在求出 \(g\) 身上。我们不难发现,如果把最外层的那对括号扒掉,不就剩下一个形如 \(f\) 的括号串了吗?所以,我们只需要在 \(f_{i - 2}\) 的基础上,外层包裹一对括号即可:\(g_{i, u, v} = \sum \limits _ {tp = 1} ^ {p} \sum \limits _ {(u', tp) \in \operatorname{out}(u)} \sum \limits _ {(v', tp) \in \operatorname{in}(v)} f_{i - 2, u', v'}\)。
答案没什么好说的,就是 \(\sum \limits _ {i = 0} ^ k h_{i, 1, n}\)。
时间复杂度 \(\Theta(kn^4 + k^2n^3) = \Theta(kn^3(n + k))\),空间复杂度 \(\Theta(kn^2)\)。
代码
#include <cstdio>
const int N = 51;
using mint = unsigned short;
const mint mod = 10007;
inline mint add(mint a, mint b) { return a >= mod - b ? a + b - mod : a + b; }
inline mint mul(mint a, mint b) { return int(a) * b % mod; }
inline void toadd(mint &a, mint b) { a = add(a, b); }
int n, m, k;
char out[N][N], in[N][N], op;
bool emp[N][N];
mint f[N][N][N], h[N][N][N], g[N][N][N];
signed main() {
scanf("%d%d%d", &n, &m, &k);
for (int u, v; m--; ) {
scanf("%d%d", &u, &v);
while ((op = getchar()) == ' ');
if ('A' <= op && op <= 'Z') out[u][v] = op - 'A' + 'a';
else if ('a' <= op && op <= 'z') in[u][v] = op;
else emp[u][v] = true;
}
for (int i = 1; i <= n; ++i) f[0][i][i] = h[0][i][i] = 1;
for (int i = 1; i <= k; ++i) {
if (i >= 2) {
for (int u = 1; u <= n; ++u)
for (int $u = 1; $u <= n; ++$u) if (out[u][$u])
for (int v = 1; v <= n; ++v)
for (int $v = 1; $v <= n; ++$v) if (out[u][$u] == in[$v][v])
toadd(g[i][u][v], f[i - 2][$u][$v]);
}
for (int w = 1; w <= n; ++w)
for (int u = 1; u <= n; ++u)
for (int v = 1; v <= n; ++v) {
if (emp[u][w]) toadd(f[i][u][v], f[i - 1][w][v]);
if (emp[u][w] || out[u][w]) toadd(h[i][u][v], h[i - 1][w][v]);
for (int l = 1; l <= i; ++l) {
toadd(f[i][u][v], mul(g[l][u][w], f[i - l][w][v]));
toadd(h[i][u][v], mul(g[l][u][w], h[i - l][w][v]));
}
}
}
mint ans = 0;
for (int i = 0; i <= k; ++i) toadd(ans, h[i][1][n]);
printf("%hu", ans);
return 0;
}
本文作者:XuYueming,转载请注明原文链接:https://www.cnblogs.com/XuYueming/p/18500393。
若未作特殊说明,本作品采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。