LGV 引理
LGV 引理
全称 \(Lindstrom-Gessel-Viennot lemma\),可以用于求解 \(DAG\) 上不相交路径条数,它的内容大致是这样的:
对于一张有向无环图,每条边上都有一个权值 \(w(e)\),我们记 \(weight(P)\) 表示路径 \(P\) 上所有点权值的乘积,那么对于一个起点组成的集合 \(A\) 和终点组成的集合 \(B\),满足 \(|A|=|B|\) 且 \(A∩B=∅\),我们再记 \(e(i,j)\) 表示所有 \(Ai→Bj\) 路径的 \(weight\) 之和,那么对于矩阵
那么 LGV 的引理的内容可以描述为:
\(detM=∑p(−1)τ(p)C(p)\)
其中 \(p\) 为 \(1∼|A|\) 的排列,\(τ(p)\) 为 \(p\) 的逆序对数,\(C(p)\) 表示有多少个由 \(|A|\) 条路径组成的 \(|A|\) 元组 \((P1,P2,⋯,P|A|)\),满足 \(Pi\) 为 \(Ai→Bpi\) 的路径,且 \(∀i,j,Pi∩Pj=∅\)。
也就是说 \(M\) 的行列式为所有 \(A\) 到 \(B\) 不相交路径的带符号和。
证明
考虑将行列式展开,得到:
\(detM=∑p(−1)τ(p)∏i=1|A|e(i,pi)=∑p(−1)τ(p)∑Pweight(P)[Pi:Ai→Bpi]\)
记 \(S1\) 表示所有相交路径组组成的集合,\(S2\) 表示所有不相交路径组组成的集合,那么上式可进一步写作
\(detM=∑p(−1)τ(p)∑Pweight(P)[Pi:Ai→Bpi][P∈S1]+∑p(−1)τ(p)∑Pweight(P)[Pi:Ai→Bpi][P∈S2]\)
而引理内容等价于:
\(detM=∑p(−1)τ(p)∑Pweight(P)[Pi:Ai→Bpi][P∈S1]\)
因此我们要证明:
\(∑p(−1)τ(p)∑Pweight(P)[Pi:Ai→Bpi][P∈S2]=0\)
我们考虑对于 \(S2\) 的元素 \(P\) 构造一个双射 \(f:S2→S2\):记 \((i,j)\) 为字典序最小的二元组满足 \(Ai→Bpi\) 与 \(Aj→Bpj\) 的路径有交,那么考虑从重合部分结束的位置交换两条路径。
那么得到的新的路径组 \(f(P)=P′\) 显然满足 \(weight(P)=weight(P′)\),而 \(P′\) 对应的排列 \(p′\) 相当于在 \(p\) 中交换了 \(pi\) 和 \(pj\),根据排列中交换两个元素,逆序对奇偶性改变可知 \((−1)τ(p)+(−1)τ(p′)=0\),因此有 \((−1)τ(σ(P))weight(P)+(−1)τ(σ(f(P)))weight(f(P))=0\),其中 \(σ(P)\) 为 \(P\) 对应的排列,又对于某个相交路径组 \(P\),必定满足 \(f(P)\) 中字典序最小的,满足 \(Ai→Bpi∩Aj→Bpj≠∅\) 的二元组 \((i,j)\) 与 \(P\) 字典序最小的二元组相同,因此 \(f(f(P))=P\),故 \(f\) 构成一个双射。因此有
\(∑p(−1)τ(p)∑Pweight(P)[Pi:Ai→Bpi][P∈S2]\)
\(=1/2*(∑p(−1)τ(p)∑Pweight(P)[Pi:Ai→Bpi][P∈S2]+∑p(−1)τ(f(p))∑Pweight(f(P))[Pi:Ai→Bpi][P∈S2])\)
\(=1/2*(∑P∈S2(−1)τ(p)weight(P)+(−1)τ(f(p))weight(f(P)))\)
\(=0\)
注意点
注意,在 OI 问题中我们常常遇到这样的问题:给定 \(n\) 个起点 \(a1,a2,⋯,an\) 和 \(n\) 个终点 \(b1,b2,⋯,bn\),问有多少个路径 \(n\) 元组 \((P1,P2,⋯,Pn)\) 满足 \(Pi\) 为 \(ai→bi\) 的路径且它们两两互不相交,此时直接套 LGV 引理是不可以的,因为有可能出现 \(p≠(1,2,3,⋯,n)\),却也被统计入答案的情况,不过对于一些特殊的图,譬如网格图,如果我们发现如果 \(p\) 中存在逆序对,就必然会出现两个路径相交,那么就可以直接通过行列式得出答案,否则你行列式算出来值的组合意义是,如果我们把图画在一个平面上并且将起点集合和终点集合从上自下一字排开,\(detM\) 就是存在偶数个交点的路径组条数减去存在奇数个交点的路径组条数。
【模板】LGV 引理
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int max_n=1e6+5;
int fac[max_n<<1],inv[max_n<<1],inv_fac[max_n<<1],now=1;
const int max_m=100+5;
int a[max_m],b[max_m];
const int mod=998244353;
inline int qpow(int a,int n)
{
int res=1;
while(n)
{
if(n&1)
res=1ll*res*a%mod;
a=1ll*a*a%mod;
n>>=1;
}
return res;
}
inline int C(int n,int m)
{
return 1ll*fac[n]*inv_fac[m]%mod*inv_fac[n-m]%mod;
}
int M[max_m][max_m];
inline int det()
{
int res=1;
bool flag_neg=false;
for(int i=1;i<=m;++i)
{
int k=i;
while(k<=m&&!M[k][i])
++k;
if(k>m)
return 0;
if(k!=i)
{
for(int j=i;j<=m;++j)
swap(M[i][j],M[k][j]);
flag_neg^=1;
}
res=1ll*res*(M[i][i]+mod)%mod;
int t=qpow(M[i][i],mod-2);
for(int k=i+1;k<=m;++k)
{
int t0=1ll*t*M[k][i]%mod;
for(int j=i;j<=m;++j)
M[k][j]=(M[k][j]-1ll*M[i][j]*t0)%mod;
}
}
return flag_neg?mod-res:res;
}
int main()
{
fac[0]=inv_fac[0]=1;
fac[1]=inv_fac[1]=inv[1]=1;
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
while(now<2*n)
{
++now;
fac[now]=1ll*fac[now-1]*now%mod;
inv[now]=1ll*(mod-mod/now)*inv[mod%now]%mod;
inv_fac[now]=1ll*inv_fac[now-1]*inv[now]%mod;
}
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)
M[i][j]=a[i]<=b[j]?C(b[j]-a[i]+n-1,n-1):0;
printf("%d\n",det());
}
return 0;
}