LGV 引理
LGV 引理
前言:欢迎来到线性代数最最高城—— LGV 引理。(书接上文)
写在矩阵树定理之前
关于矩阵的行列式的内容大家可以前往矩阵树定理相关内容查看。
从而言之,就是:
其中, \(|A|\) 也能被记作 \(\det(A)\) 。在本文中,我们使用第一种表示方式。
行列式的意义是:我们对所有排列 \(P\) 的情况计数求和。每个排列 \(P\) 的权值定义为 \((-1)^k\prod_{i=1}^nA_{i,P_i}\) ,\(k\) 为排列的逆序对数。
有人可能会认为 \((-1)^k\) 的形式不太美观,为什么行列式的定义这么难看。其实恰恰相反。我们可以发现,如果我们交换排列中的两个元素。行列式正好能抵消其中的一些部分。这样相互抵消往往符合一些奇奇怪怪的要求/条件。
简要概述一下如何求出 \(|A|\) :
我们发现: \(|I|=(-1)^0=1\) 。
高斯消元本质上是将原矩阵经过一些初等变换矩阵转换为 \(I\) 。我们将高斯消元的过程倒过来,在高斯消元中:
-
交换 \(A\) 中的两行:\(|A^{'}|=|A|\) 。
-
将其中一行乘上 \(K\) : \(A^{'}|=K\cdot|A|\) 。
-
将其中一行加上另一行的 \(K\) 倍:\(|A|=|A^{'}|\) 。
其中 \(A^{'}\) 为每次变换后的矩阵,其中证明参见矩阵树定理。
LGV 引理
LGV 引理可以用来解决有向无环图上不相交路径等问题。只适用于有向无环图。
我们定义 \(\omega(P)\) 为 \(P\) 这条路径的所有边权之积, \(e(x,y)\) 为从 \(x\) 到 \(y\) 的所有路径的 \(\omega(P)\) 之和,即:
对于起点 \(s\in S\) 和 \(t\in T\) ,我们假设 \(S\) 和 \(T\) 的集合大小都为 \(n\) ,我们定义 \(A\):
和矩阵树定理类似,我们将边权设为 1 , \(|A|\) 即为 \(S\) 到 \(T\) 的不相交路径组的方案数,其中 \(s_1\) 连接 \(t_1\) ,\(s_2\) 连接 \(t_2\) ,\(s_i\) 连接 \(t_i\) ,以此类推。
LGV 引理本质上是在求这样一个问题,对于一个从 \(S\) 到 \(T\) 的不相交路径组的方案:
注意:这里的 \(w(e)\) 是边权,区别于上文提到的路径边权积 \(\omega(P)\) 。一个是小写字母 w ,一个是希腊字母 omega 。
简要证明正确性:
我们先以此连接 \(s_i\) 和 \(t_i\) ,先不管其中相交的路径。这时,我们将方案集合 \(C\) 分明的分为两部分,一部分是有相交的路径,另一部分是没有相交的路径(我们要求的)。
对于有相交的路径,我们交换交点后的路径,新的路径可以看作不相交的。这样我们所有的方案都变成合法的了。
但是这不满足 \(s_i\) 和 \(t_i\) 一一对应的要求,那么我们考虑容斥,交换 \(i\) 组边让其中一些非法的情况变成合法的情况,然后我们将其减去。
减去的路径中还有算重的,环环相扣,我们写出式子:
这这这不就是行列式吗?
我们对 \(A\) 做高斯消元,维护行列式,就得到的问题的答案。
应用
Solution
统计方案数,我们将边权 \(w(e)\) 全设为 1 。其中 $\omega((a,b),(c,d))=\binom{c-a+d-b}{c-a} $ 。
直接做高斯消元就完成了。
代码如下:
#include<bits/stdc++.h>
#define x first
#define y second
typedef long long ll;
using namespace std;
const int N=1e6+10,M=110,mod=998244353;
ll power(ll a,ll b){
ll ans=1;
for(;b;b>>=1){
if(b&1)ans=ans*a%mod;
a=a*a%mod;
}
return ans;
}
int n,m,a[N],b[N];
ll A[M][M],f[N<<1],incf[N<<1];
void init(int n){
f[0]=1;
for(int i=1;i<=n;i++)f[i]=f[i-1]*i%mod;
incf[n]=power(f[n],mod-2);
for(int i=n-1;i>=0;i--)incf[i]=incf[i+1]*(i+1)%mod;
}
ll C(ll a,ll b){
return f[a]*incf[b]%mod*incf[a-b]%mod;
}
ll Guess(){
ll op=1,ans=1;
for(int i=1;i<=m;i++){
int pos=i;
for(int j=i+1;j<=m;j++){
if(A[j][i]>A[pos][i])pos=j;
}
if(i!=pos)op=-op,swap(A[i],A[pos]);
if(!A[i][i])return 0;
for(int j=1;j<=m;j++){
if(i==j)continue;
ll tmp=A[j][i]*power(A[i][i],mod-2)%mod;
for(int k=2;k<=m;k++)
A[j][k]=(A[j][k]-tmp*A[i][k]%mod+mod)%mod;
}
ans=ans*A[i][i]%mod;
ll tmp=power(A[i][i],mod-2);
for(int j=1;j<=m;j++)
A[i][j]=A[i][j]*tmp%mod;
}
return (op*ans+mod)%mod;
}
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++)
A[i][j]=(a[i]<=b[j]?C(b[j]-a[i]+n-1,n-1):0);
printf("%lld\n",Guess());
}
int main(){
init((N<<1)-1);
int T;scanf("%d",&T);
while(T--)solve();
return 0;
}

浙公网安备 33010602011771号