Introduction
The Lindström–Gessel–Viennot lemma, or LGV lemma, can be used for problems such as counting vertex-disjoint paths in directed acyclic graphs.
Prerequisites: the basic material in Graph-theoretic concepts, Matrices, and Gaussian Elimination for Determinants.
The LGV lemma applies only to directed acyclic graphs.
Definition
denotes the product of the edge weights along path . (For path counting, all edge weights can be set to .) (In fact, edge weights may be generating functions.)
denotes the sum of over every path from to , that is, .
The source set is a subset of the vertex set of the directed acyclic graph, with size .
The target set is also a subset of the vertex set of the directed acyclic graph, also with size .
A vertex-disjoint path family from : is a path from to , where is a permutation; for any , and have no common vertex.
denotes the number of inversions in permutation .
Lemma
Here, denotes the sum over every vertex-disjoint path family satisfying the requirements above.
Proof
By the definition of a determinant,
Observe that is exactly the sum of over all path families from to whose permutation is .
Here, is an arbitrary path family.
Let be a vertex-disjoint path family and an intersecting path family.
Suppose contains an intersecting pair . There is necessarily a corresponding intersecting pair ; all other paths in are the same as in . Therefore .
Thus .
Consequently, .
This proves the lemma.1
Examples
Example 1: CF348D Turtles
Problem statement: An grid contains passable and impassable cells. A turtle at may move only to or . Find the number of vertex-disjoint paths from to modulo . .
This is a direct application of the LGV lemma. Consider all valid paths. Every path from must pass through , and every path reaching the destination must pass through . Thus can be selected immediately. Applying the LGV lemma gives:
Here, is the number of paths from in the graph. The path-counting problem with blocked cells can be solved by an DP, so is easy to obtain. The final complexity is .
Reference implementation
#include <cstring>
#include <iostream>
#include <string>
using namespace std;
using ll = long long;
constexpr int MOD = 1e9 + 7;
constexpr int SIZE = 3010;
string board[SIZE];
int dp[SIZE][SIZE];
int f(int x1, int y1, int x2, int y2) {
memset(dp, 0, sizeof dp);
dp[x1][y1] = board[x1][y1] == '.';
for (int i = 1; i <= x2; i++) {
for (int j = 1; j <= y2; j++) {
if (board[i][j] == '#') {
continue;
}
dp[i][j] = (dp[i][j] + dp[i - 1][j]) % MOD;
dp[i][j] = (dp[i][j] + dp[i][j - 1]) % MOD;
}
}
return dp[x2][y2] % MOD;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> board[i];
board[i] = " " + board[i];
}
ll f11 = f(1, 2, n - 1, m);
ll f12 = f(1, 2, n, m - 1);
ll f21 = f(2, 1, n - 1, m);
ll f22 = f(2, 1, n, m - 1);
ll ans = ((f11 * f22) % MOD - (f12 * f21) % MOD + MOD) % MOD;
cout << ans << '\n';
return 0;
}
Example 2: HDU 5852 Intersection is not allowed!
Problem statement: An board allows a piece at to move only to or . There are pieces; piece starts at and must end at . The paths must be pairwise disjoint. Find the number of solutions modulo . , , and , .
If the paths are disjoint, they must connect to , so in the LGV lemma we necessarily have and need not consider signs. Set every edge weight to and apply the lemma directly.
The number of paths from to is the number of ways to choose downward steps from steps, so .
The determinant can be computed with Gaussian elimination.
The complexity is , where is the complexity of computing a modular inverse.
Reference implementation
#include <algorithm>
#include <iostream>
using ll = long long;
constexpr int K = 105;
constexpr int N = 100005;
constexpr int mod = 1e9 + 7;
int T, n, k, a[K], b[K], fact[N << 1], m[K][K];
int qpow(int x, int y) {
int out = 1;
while (y) {
if (y & 1) out = (ll)out * x % mod;
x = (ll)x * x % mod;
y >>= 1;
}
return out;
}
int c(int x, int y) {
return (ll)fact[x] * qpow(fact[y], mod - 2) % mod *
qpow(fact[x - y], mod - 2) % mod;
}
using std::cin;
using std::cout;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
fact[0] = 1;
for (int i = 1; i < N * 2; ++i) fact[i] = (ll)fact[i - 1] * i % mod;
cin >> T;
while (T--) {
cin >> n >> k;
for (int i = 1; i <= k; ++i) cin >> a[i];
for (int i = 1; i <= k; ++i) cin >> b[i];
for (int i = 1; i <= k; ++i) {
for (int j = 1; j <= k; ++j) {
if (a[i] <= b[j])
m[i][j] = c(b[j] - a[i] + n - 1, n - 1);
else
m[i][j] = 0;
}
}
for (int i = 1; i < k; ++i) {
if (!m[i][i]) {
for (int j = i + 1; j <= k; ++j) {
if (m[j][i]) {
std::swap(m[i], m[j]);
break;
}
}
}
if (!m[i][i]) continue;
int inv = qpow(m[i][i], mod - 2);
for (int j = i + 1; j <= k; ++j) {
if (!m[j][i]) continue;
int mul = (ll)m[j][i] * inv % mod;
for (int p = i; p <= k; ++p) {
m[j][p] = (m[j][p] - (ll)m[i][p] * mul % mod + mod) % mod;
}
}
}
int ans = 1;
for (int i = 1; i <= k; ++i) ans = (ll)ans * m[i][i] % mod;
cout << ans << '\n';
}
return 0;
}
References
Footnotes
-
The proof is from Zhihu - Proof of the LGV Lemma ↩