LGV 引理

LGV 引理

前言:欢迎来到线性代数最最高城—— LGV 引理。(书接上文)

写在矩阵树定理之前

关于矩阵的行列式的内容大家可以前往矩阵树定理相关内容查看。

从而言之,就是:

\[|A|=\sum_P(-1)^k\prod_{i=1}^nA_{i,P_i} \]

其中, \(|A|\) 也能被记作 \(\det(A)\) 。在本文中,我们使用第一种表示方式。

行列式的意义是:我们对所有排列 \(P\) 的情况计数求和。每个排列 \(P\) 的权值定义为 \((-1)^k\prod_{i=1}^nA_{i,P_i}\)\(k\) 为排列的逆序对数。

有人可能会认为 \((-1)^k\) 的形式不太美观,为什么行列式的定义这么难看。其实恰恰相反。我们可以发现,如果我们交换排列中的两个元素。行列式正好能抵消其中的一些部分。这样相互抵消往往符合一些奇奇怪怪的要求/条件。

简要概述一下如何求出 \(|A|\)

我们发现: \(|I|=(-1)^0=1\)

高斯消元本质上是将原矩阵经过一些初等变换矩阵转换为 \(I\) 。我们将高斯消元的过程倒过来,在高斯消元中:

  1. 交换 \(A\) 中的两行:\(|A^{'}|=|A|\)

  2. 将其中一行乘上 \(K\)\(A^{'}|=K\cdot|A|\)

  3. 将其中一行加上另一行的 \(K\) 倍:\(|A|=|A^{'}|\)

其中 \(A^{'}\) 为每次变换后的矩阵,其中证明参见矩阵树定理

LGV 引理

LGV 引理可以用来解决有向无环图上不相交路径等问题。只适用于有向无环图。

我们定义 \(\omega(P)\)\(P\) 这条路径的所有边权之积, \(e(x,y)\) 为从 \(x\)\(y\) 的所有路径的 \(\omega(P)\) 之和,即:

\[e(x,y)=\sum_{P:x\rightarrow y}\omega(P) \]

对于起点 \(s\in S\)\(t\in T\) ,我们假设 \(S\)\(T\) 的集合大小都为 \(n\) ,我们定义 \(A\)

\[A=\begin{bmatrix} e(S_1,T_1) & e(S_1,T_2) & \cdots & e(S_1,T_n)\\ e(S_2,T_1) & e(S_2,T_2) & \cdots & e(S_2,T_n)\\ \vdots & \vdots & \ddots & \vdots\\ e(S_n,T_1) & e(S_n,T_2) & \cdots & e(S_n,T_n) \end{bmatrix} \]

和矩阵树定理类似,我们将边权设为 1 , \(|A|\) 即为 \(S\)\(T\) 的不相交路径组的方案数,其中 \(s_1\) 连接 \(t_1\)\(s_2\) 连接 \(t_2\)\(s_i\) 连接 \(t_i\) ,以此类推。

LGV 引理本质上是在求这样一个问题,对于一个从 \(S\)\(T\) 的不相交路径组的方案:

\[|A|=\sum_C\prod_{e\in C}w(e) \]

注意:这里的 \(w(e)\) 是边权,区别于上文提到的路径边权积 \(\omega(P)\) 。一个是小写字母 w ,一个是希腊字母 omega 。

简要证明正确性:

我们先以此连接 \(s_i\)\(t_i\) ,先不管其中相交的路径。这时,我们将方案集合 \(C\) 分明的分为两部分,一部分是有相交的路径,另一部分是没有相交的路径(我们要求的)。

对于有相交的路径,我们交换交点后的路径,新的路径可以看作不相交的。这样我们所有的方案都变成合法的了。

但是这不满足 \(s_i\)\(t_i\) 一一对应的要求,那么我们考虑容斥,交换 \(i\) 组边让其中一些非法的情况变成合法的情况,然后我们将其减去。

减去的路径中还有算重的,环环相扣,我们写出式子:

\[\sum_P(-1)^k\prod_{i=1}^nA_{i,P_i} \]

这这这不就是行列式吗?

我们对 \(A\) 做高斯消元,维护行列式,就得到的问题的答案。

应用

P6657【模板】LGV 引理 - 洛谷

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;
}
posted @ 2024-10-04 17:58  lichenyu_ac  阅读(97)  评论(0)    收藏  举报