LGV 引理 学习笔记

引入

LGV 引理可以解决图上不相交路径计数问题。

注意,LGV 引理只适用于有向无环图

定义

\(\omega(P)\) 表示路径 \(P\) 的边权积。

\(e(u,v)\) 表示 \(u\)\(v\) 的所有路径的边权积的和 \(e(u,v) = \sum\limits_{S:u \rightarrow v} \omega(S)\),当边权为 \(1\) 时,也可以表示为 \(u\)\(v\) 的路径总数。

起点集合 \(A\),有向无环图上的 \(n\) 个点构成的子集。

终点集合 \(B\),有向无环图上的 \(n\) 个点构成的子集。

\(A \rightarrow B\) 的一个不相交路径组 \(S\),满足 \(S_i\) 是一条 \(A_i\)\(B_{\sigma(S)_i}\) 的路径,\(\sigma(S)\) 是一个排列,满足 \(\forall i,j \land i \ne j\)\(S_i\)\(S_j\) 没有公共顶点。

\(\tau(\sigma)\) 表示排列 \(\sigma\) 的逆序对个数。

引理

对于一个行列式:

\[M = \left[ \begin {matrix} e(A_1, B_1) & e(A_1, B_2) & \cdots & e(A_1, B_n) \\ e(A_2, B_1) & e(A_2, B_2) & \cdots & e(A_2, B_n) \\ \vdots & \vdots & \ddots & \vdots \\ e(A_n, B_1) & e(A_n, B_2) & \cdots & e(A_n, B_n) \\ \end {matrix} \right] \]

\(\text{det}(M) = \sum\limits_{S:A \rightarrow B} (-1)^{\tau(\sigma(S))} \prod\limits_{i = 1}^{n}\omega(S_i)\)

\(S\) 表示不相交路径组的每一条 \(S\)
因此,\(\text{det}(M)\) 表示不相交路径组的边权积的带符号和,当边权等于一时,就等于不相交路径组方案数的带符号和。

证明

由行列式的定义可得:

\[\begin{aligned} {} \text{det}(M) & = \sum\limits_\sigma (-1)^{\tau(\sigma)}\prod\limits_{i = 1} ^ n e(A_i,b_{\sigma_i}) \\ & = \sum\limits_\sigma (-1)^{\tau(\sigma)}\prod\limits_{i = 1} ^ n \sum\limits_{E:A_i \rightarrow B_{\sigma_i} } \omega(E) \end{aligned} \]

显然可以发现:\(\prod\limits_{i = 1} ^ n \sum\limits_{E:A_i \rightarrow B_{\sigma_i} } \omega(E)\) 其实是,点集 \(A\) 到点集 \(B\) 的所有路径组 \(S\)\(\omega(S_i)\) 的和。

即:

\[\begin{aligned} \text{det}(M) &= \sum\limits_{S:A \rightarrow B} (-1) ^ {\tau(\sigma)} \prod\limits_{i = 1}^n \omega(S_i) \end{aligned} \]

此时可以将路径分为两类,一种是相交路径组 \(U\),另一种是不相交路径组 \(V\)

\[\begin{aligned} \text{det}(M) &= \sum\limits_{S:A \rightarrow B} (-1) ^ {\tau(\sigma(S))} \prod\limits_{i = 1}^n \omega(S_i)\\ &= \sum\limits_{U:A \rightarrow B} (-1) ^ {\tau(\sigma(U))} \prod\limits_{i = 1}^n \omega(U_i) + \sum\limits_{V:A \rightarrow B} (-1) ^ {\tau(\sigma(V))} \prod\limits_{i = 1}^n \omega(V_i) \end{aligned} \]

此时只需要证明 \(\sum\limits_{V:A \rightarrow B} (-1) ^ {\tau(\sigma(V))} \prod\limits_{i = 1}^n \omega(V_i) = 0\) 即可。

那么可以考虑两条 \(V\) 中路径,\(V_i = A_i \rightarrow u \rightarrow B_{\sigma_i}, V_j = A_j \rightarrow u \rightarrow B_{\sigma_j}\)

此时就肯定存在 \(V_x = A_i \rightarrow u \rightarrow B_{\sigma_j}, V_y = A_j \rightarrow u \rightarrow B_{\sigma_i}\)

此时,\(\omega(V_i) \times \omega(V_j) = \omega(V_x) \times \omega(V_y)\)

而两者因为交换了 \(B_{\sigma_i}\)\(B_{\sigma_j}\),所以两者的排列的奇偶性变化,又因为对于 \(\forall i,j\) 都存在对应的 \(x,y\) 使得 \(\omega(V_i) \times \omega(V_j) = \omega(V_x) \times \omega(V_y)\) 且排列的奇偶性不同。

\(\therefore \sum\limits_{V:A \rightarrow B} (-1) ^ {\tau(\sigma)} \prod\limits_{i = 1}^n \omega(V_i) = 0\)

因此 \(\text{det}(M) = \sum\limits_{U:A \rightarrow B} (-1) ^ {\tau(\sigma(U))} \prod\limits_{i = 1}^n \omega(U_i)\)

实现

以下为 【模板】LGV 引理 代码。

#include <cstdio>
#include <cmath>
#include <algorithm>

using u32 = unsigned int ;
using i64 = long long ;
using u64 = unsigned long long ;

const int N = 105 ;
const int mod = 998244353 ;

i64 fac[N * N * N * 2], inv[N * N * N * 2];
void prework(){
    fac[0] = 1;
    inv[0] = inv[1] = 1;
    for (int i = 1; i <= 2000000; i++) {
        fac[i] = fac[i - 1] * i % mod;
    }

    for (int i = 2; i <= 2000000; i++) {
        inv[i] = (mod - mod / i) * inv[mod % i] % mod;
    }

    for (int i = 1; i <= 2000000; i++) {
        inv[i] = inv[i - 1] * inv[i] % mod;
    }
}

i64 C(int x, int y){
    return fac[x] * inv[y] % mod * inv[x - y] % mod;
}

int n, m;
int a[N], b[N];

i64 ans;
i64 mat[N][N];
void Gauss(){
    for (int i = 1; i <= m; i++) {
        for (int j = i + 1; j <= m; j++) {
            while (mat[i][i]) {
                i64 val = mat[j][i] / mat[i][i];

                for (int k = 1; k <= m; k++) {
                    mat[j][k] = (mat[j][k] - mat[i][k] * val % mod + mod) % mod;
                }

                std::swap(mat[i], mat[j]);
                ans *= -1;
            }

            std::swap(mat[i], mat[j]);
            ans *= -1;
        }
    }
}

void solve(){
    scanf("%d %d", &n, &m);

    for (int i = 1; i <= m; i++) {
        scanf("%d %d", a + i, b + i);
    }

    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= m; j++) {
            mat[i][j] = b[j] < a[i]? 0: C(b[j] - a[i] + n - 1, b[j] - a[i]);//通过组合数,求解两点之间路径数量
        }
    }

    ans = 1;
    Gauss();

    for (int i = 1; i <= m; i++) {
        ans = (ans * mat[i][i] % mod + mod) % mod;
    }

    printf("%lld\n", ans);
}

int main(){
    prework();
    int T;
    scanf("%d", &T);

    while (T--) {
        solve();
    }
    return 0;
}
posted @   Z_drj  阅读(83)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示