1. 程式人生 > 其它 >LGV 引理

LGV 引理

\(LGV\) 引理

在有向無環圖中,有一組起點 \(A = \{a_1,a_2……a_n \}\)終點 \(B=\{b_1,b_2……b_n \}\)

定義 \(w(P)\) 為一條路徑 \(P\) 中每條邊權的乘積,即 \(w(P)=\prod_{e\in P}v_e\)

定義 \(e(a,b)\) 為兩點之間所有路徑的 \(w\) 之和,即 \(e(a,b)=\sum_{P:a->b} w(P)\)

設一個從 \(A\)\(B\) 的路徑組為 \(P=(P_1,P_2……P_n),P_i = (a_i,b_{r(i)})\),其中 \(r\) 為任意一個 \(1\)

\(n\) 的排列。

則定義 \(w(P)=\prod_{i = 1}^n w(P_i)\)

定義 \(t(P)\) 為該路徑對應的排列 \(r\) 的逆序對數。

那麼 \(LGV\) 引理描述為,設一個矩陣

\[M = \begin{pmatrix}e(a_1,a_2)\ e(a_1,b_2) \ … \ e(a_1,b_n)\\ e(a_2,b_1) \ e(a_2,b_2) \ … \ e(a_2,b_n) \\ …… \\ e(a_n,b_1) \ e(a_n,b_2) \ … \ e(a_n,b_n) \end{pmatrix} \]

則有定理

\(det(M)=\sum_{P:A->B} (-1)^{t(P)} \prod_{i=1}^n w(P_i)\)

其中 \(P\) 是一個從 \(A\)\(B\) 的不相交路徑組。

該定理的實際意義為,\(det(M)\) 的值是所有不相交路徑組方案數的帶符號數量和。

證明:

由行列式定義知

\[det(M) = \sum_{r} (-1)^{\tau (r)}\prod _{i =1}^n e(a_i,b_{r_i}) = \sum_{r}(-1)^{\tau (r)}\prod_{i=1}^n \sum_{P:a_i->b_{r_i}} w(P) \]

觀察 \(\prod_{i=1}^n \sum_{P:a_i->b_{r_i}} w(P)\),實際上是所有從 \(A\)\(B\)

排列為 \(r\) 的路徑組 \(P\)\(w(P)\) 之和。

\(\sum_r (-1)^{\tau (r)}\prod_{i=1}^n \sum_{P:a_i->b_{r_i}}w(P) = \sum_r (-1)^{\tau (r)}\sum_{P=r}w(P)=\sum_{P:A->B}(-1)^{t(P)}\prod_{i=1}^n w(P_i)\)

實際上,這裡的 \(P\) 是一個任意路徑組,描述的並非不相交路徑組

\(U\) 為不相交路徑組,\(V\) 為相交路徑組,則:

\[\sum_{P:A->B}(-1)^{t(P)}\prod_{i=1}^n w(P_i) = \sum_{U:A->B} (-1)^{t(U)}\prod_{i=1}^n w(U_i)+\sum_{V:A->B}(-1)^{t(V)}\prod_{i=1}^n w(V_i) \]

只需證

\[\sum_{V:A->B}(-1)^{t(V)}\prod_{i=1}^n w(V_i) = 0 \]

定理即得證。

實際上,我們在一組相交路徑 \(P\) 中取一個相交路徑中最小的二元組 \((i,j)\)

有路徑 \(P_i:a_1->u->b_1,P_j=a_2->u->b_2\)

稍作更改,可得 \(P_i:a_1->u->b_2,P_j=a_2->u->b_1\),我們通過這種構造得到一組新的相交路徑 \(P'\),容易發現:

\[w(P) = w(P'),t(P)=t(P')±1 \]

對於任意相交路徑,都能通過這樣的交換使其一一對應,相互抵消,故而引理得證。

例題

\(CF348D\) \(Turtles\)

一張 \(n\)\(m\) 列的網格圖,圖的某些格子上有障礙物,求出滿足 \((1,1)\)\((n,m)\) 的兩條不相交且均不經過障礙物的路徑個數,答案對 \(10^9 +7\) 取模,\((x,y)\) 一步只能到達 \((x +1,y)\)\((x,y+1)\)

\(2\leq n,m\leq 3\times 10^3\)

\(Sol\)

看到不相交,考慮 \(LGV\) 引理。

因為所有路徑不能在起點終點處相交,選定起點集合 \(A=\{(1,2),(2,1) \}\),終點集合 \(B=\{(n-1,m),(m-1,n) \}\) ,邊權均設為 \(1\)。跑 \(LGV\) 引理即可,其中 \(e_{i,j}\) 可以通過 \(dp\) 求解。此時 \(det(M)\) 即為答案,這樣做是對的原因是因為當且僅當 \(r=(1,2)\) 時路徑才不會交叉,此時 \(\tau(r)=1\)。時間複雜度 \(O(nm)\)

\(code\)

#include <bits/stdc++.h>
using namespace std;
const int N = 3e3 + 10, mod = 1e9 + 7;
inline int read()
{
	int s = 0, w = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') w *= -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
	return s * w;
}
char mp[N][N];
int n, m, dp[N][N];
inline int f(int x1, int y1, int x2, int y2)
{
	if(mp[x1][y1] == '#' || mp[x2][y2] == '#') return 0;
	memset(dp, 0, sizeof(dp)), dp[x1][y1] = 1;
	for(register int i = 1; i <= x2; i++)
		for(register int j = 1; j <= y2; j++)
			if(mp[i][j] == '.') (dp[i][j] += dp[i - 1][j]) %= mod, (dp[i][j] += dp[i][j - 1]) %= mod;
	return dp[x2][y2];
}
int main()
{
	n = read(), m = read();
	for(register int i = 1; i <= n; i++) scanf("%s", mp[i] + 1);
	int f11 = f(1, 2, n - 1, m), f12 = f(1, 2, n, m - 1), f21 = f(2, 1, n - 1, m), f22 = f(2, 1, n, m - 1);
	int ans = ((1ll * f11 * f22 % mod - 1ll * f21 * f12 % mod) % mod + mod) % mod;
	printf("%d\n", ans);
	return 0;
}

\(LuoGu \ P6657\) \([\)模板\(]\) \(LGV\) 引理

\(T\) 組資料,每組給出一個 \(n\times n\) 的棋盤,棋子從 \((x,y)\) 一步只能走到 \((x+1,y)\)\((x,y+1)\) ,有 \(m\) 個棋子,初始時第 \(i\) 個放在 \((1,a_i)\),有 \(m\) 個終點,第 \(i\) 個終點是 \((n,b_i)\) 。求出有多少種方案,能使每個棋子都能從起點走到終點,且對於所有的棋子它們的路徑不交,答案對 \(998244353\) 取模。

\(1\leq T\leq 5,2\leq n\leq 10^6,1\leq m\leq 100,1\leq a_1\leq a_2\leq … \leq a_m\leq n,1\leq b_1 \leq b_2\leq … \leq b_m \leq n\)

\(Sol\)

不相交路徑,考慮 \(LGV\) 引理。

注意到對於本題,只有當 \(r=(1,2…m)\) 時才能找到這樣的路徑,此時 \(\tau(r) = 1\),可以套用 \(LGV\) 引理直接做。對於 \(e_{a_i,b_j}\) 它等於:

\[e_{a_i,b_j} = \begin{Bmatrix}\binom{b_j-a_i+n-1}{n-1} \ \ \ \ \ b_j\geq a_i \\ 0 \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ otherwise \end{Bmatrix} \]

設邊權為 \(1\),預處理階乘(組合數),求出 \(M\) 矩陣後直接做行列式就是答案,理由與上個題目相似,時間複雜度 \(O(Tm^3+n)\)

\(code\)

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int M = 1e2 + 10, N = 2e6 + 10, lim = 2e6, mod = 998244353;
inline int read()
{
	int s = 0, w = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') w *= -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
	return s * w;
}
int T, n, m;
int x[M], y[M], e[M][M];
int fac[N], inv[N];
inline int power(int x, int k)
{
	int res = 1;
	while(k){
		if(k & 1) res =  res * x % mod;
		x = x * x % mod, k >>= 1;
	}
	return res;
}
inline void initial()
{
	fac[0] = inv[0] = 1;
	for(register int i = 1; i <= lim; i++) fac[i] = fac[i - 1] * i % mod;
	inv[lim] = power(fac[lim], mod - 2);
	for(register int i = lim - 1; i >= 1; i--) inv[i] = inv[i + 1] * (i + 1) % mod;
}
inline int C(int x, int y) { return x < y ? 0 : fac[x] * inv[y] % mod * inv[x - y] % mod; }
inline int det()
{
	int res = 1, f = 1;
	for(register int j = 1; j <= m; j++){
		for(register int i = j; i <= m; i++){
			if(!e[i][j]) continue;
			if(i != j) swap(e[i], e[j]), f *= -1; //行列式交換
			break; 
		}
		if(!e[j][j]) return 0;
		res = res * e[j][j] % mod;
		int tem = power(e[j][j], mod - 2);
		for(register int k = j; k <= m; k++) e[j][k] = e[j][k] * tem % mod;
		for(register int i = j + 1; i <= m; i++)
			for(register int k = j, t = e[i][j]; k <= m; k++)
				e[i][k] = ((e[i][k] - t * e[j][k] % mod) % mod + mod) % mod;
	}
	return (res * f + mod) % mod;
}
signed main()
{
	initial();
	T = read();
	while(T--){
		n = read(), m = read();
		for(register int i = 1; i <= m; i++) x[i] = read(), y[i] = read();
		for(register int i = 1; i <= m; i++)
			for(register int j = 1; j <= m; j++) e[i][j] = (x[i] <= y[j]) ? C(y[j] - x[i] + n - 1, n - 1) : 0;
		printf("%d\n", det());
	}
	return 0;
}

\(LuoGu \ P7736\) \([NOI2021]\) 路徑交點

\(T\) 組資料,每組給出 \(k\) 層點,第 \(i\) 層點有 \(n_i\) 個,其中 \(n_1=n_k,n_1\leq n_i\leq 2n_1\)。第 \(i(1\leq i<k)\) 層的點僅會向第 \(i + 1\) 層的點連 \(m_i\) 條有向邊,第 \(k\) 層點不向任何點連邊。對於兩條路徑 \(P,Q\),設它們在第 \(j\) 層的連邊為 \((P_j,P_{j+1}),(Q_j,Q_{j+1})\),則稱這兩個路徑在第 \(j\) 層有交點,當且僅當:

\[(P_j - Q_j)(P_{j+1}-Q_{j+1})<0 \]

定義一個路徑的總相交次數為所有邊兩兩的相交次數之和。求在選出 \(n_1\) 條互不相交的路徑,滿足均以第一層點為起點,第 \(k\) 層點為終點的所有方案中,總相交次數為偶數的方案減去總相交次數為奇數的方案是多少,答案對 \(998244353\) 取模。

\(2\leq k,n_1\leq 100,1\leq T\leq 5\)

\(Sol\)

偶數減去奇數,暗示行列式,再考慮相交次數,對於 \(k=2\) 的情況,兩個路徑相交,如果順次匹配的話,當且僅當其對應的 \(r\) 中產生了一個逆序對。所以對於 \(k=2\),我們可以直接把原圖的鄰接矩陣當做 \(M\) 矩陣做 \(LGV\) 引理,這樣得到的就是相交偶數次減去相交奇數次。

原因比較顯然,考慮行列式的式子:

\[det(M) = \sum_{r} (-1)^{\tau (r)}\prod _{i =1}^n e(a_i,b_{r_i}) \]

等於是 \(\tau(r)\) 偶數的時候是正,否則是負數,和原問比較完美的契合。

對於 \(k > 2\) 的情況,一個顯然的想法是擴充套件上面的思路,\(LGV\) 引理要求的是路徑條數,那我們就把每一層對應的鄰接矩陣乘起來,就能得到方案了,記這個新矩陣為 \(M\)\(LGV\) 引理。

時間複雜度 \(O(Tn^3k)\)

\(code\)

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 3e2 + 10, mod = 998244353;
inline int read()
{
	int s = 0, w = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') w *= -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
	return s * w;
}
struct Matrix{
	int n, m, a[N][N];
	Matrix operator * (const Matrix &x){
		Matrix res; res.n = n; res.m = x.m;
		for(register int i = 1; i <= n; i++){
			for(register int j = 1; j <= x.m; j++){
				int add = 0;
				for(register int k = 1; k <= m; k++) add = (add + a[i][k] * x.a[k][j] % mod) % mod;
				res.a[i][j] = add;
			}
		}
		return res;
	}
	inline void clear(int x, int y){
		n = x, m = y;
		for(register int i = 1; i <= n; i++)
			for(register int j = 1; j <= m; j++) a[i][j] = 0;
	}
}A, B;
inline int power(int x, int k)
{
	int res = 1;
	while(k){
		if(k & 1) res = res * x % mod;
		x = x * x % mod, k >>= 1;
	}
	return res;
}
int T, n[N], m[N], e[N][N];
inline int det(int m)
{
	int res = 1, f = 1;
	for(register int i = 1; i <= m; i++)
		for(register int j = 1; j <= m; j++) e[i][j] = A.a[i][j];
	for(register int j = 1; j <= m; j++){
		for(register int i = j; i <= m; i++){
			if(!e[i][j]) continue;
			if(i != j) swap(e[i], e[j]), f *= -1; //行列式交換
			break; 
		}
		if(!e[j][j]) return 0;
		res = res * e[j][j] % mod;
		int tem = power(e[j][j], mod - 2);
		for(register int k = j; k <= m; k++) e[j][k] = e[j][k] * tem % mod;
		for(register int i = j + 1; i <= m; i++)
			for(register int k = j, t = e[i][j]; k <= m; k++)
				e[i][k] = ((e[i][k] - t * e[j][k] % mod) % mod + mod) % mod;
	}
	return (res * f + mod) % mod;
}
signed main()
{
	T = read();
	while(T--){
		int k = read();
		for(register int i = 1; i <= k; i++) n[i] = read();
		for(register int i = 1; i < k; i++) m[i] = read();
		A.clear(n[1], n[2]);
		for(register int i = 1, x, y; i <= m[1]; i++)
			x = read(), y = read(), A.a[x][y] = 1;
		for(register int i = 2; i < k; i++){
			B.clear(n[i], n[i + 1]);
			for(register int j = 1, x, y; j <= m[i]; j++)
				x = read(), y = read(), B.a[x][y] = 1;
			A = A * B;
		}
		printf("%d\n", det(n[1]));
	}
	return 0;
}

完。