[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;
}
posted @ 2024-10-24 22:03  XuYueming  阅读(8)  评论(0编辑  收藏  举报