LGV 引理笔记
给定一个有向图 ,大小相同的起点集合 和终点集合 (),每条路径的权值为路径上的边权乘积。定义一个路径组为一组 条从 中某点到 中某点的路径,使得路径两两没有公共节点,且 个点各属于恰好一条路径。定义一个路径组的权值为其中所有路径的权值乘积。如果把一个路径组 看成一个排列 (代表 去 ),求:
这个问题看起来需要非常复杂的容斥,但 LGV 引理为我们提供了一个简洁的解决手段。
我们首先构造一个矩阵 ,,其中 代表从 到 的路径数。
Lemma. 上式的值等于 的行列式的值。
在许多题目中,其实任意一个路径组的“权值”都默认是 1,也即计数合法路径组方案数相关的式子(这种情况下相当于把边权设为1)。但是不要忘记 LGV 还能用来求更具拓展性的问题。
【例】[NOI2021]路径交点
首先我们考虑一个路径组(根据题目定义,所求的路径组需要两两没有公共节点,所以就是 LGV 的题设)的“交点”个数的奇偶性与什么有关。对于一个确定的路径组,假如其中的一条路径 P1 和另一条路径 P2 在第 d 层的节点分别是 x 和 y,那把 P1 的改到 y、P2 的改到 x 不会改动 P1 和 P2 的交点个数奇偶性——只有一种情况例外:d = 1 或 K。更进一步地,我们发现如果 P1[1]<P2[1],P1[K]>P2[K],则 P1 和 P2 必然有奇数个交点。因此,一个路径组的贡献应该是 。如果学过 LGV 引理,就可以立刻通过递推出 的方式构造出 然后高斯消元求行列式从而做出此题。
复制#include <bits/stdc++.h> using namespace std; const int N=205,mod=998244353; int K,n[N],m[N],f[105][N][N],a[105][105]; vector<int>G[105][N]; inline void add(int &x,int y){(x+=y)>=mod&&(x-=mod);} int main(){ int T; cin>>T; while(T--){ memset(f,0,sizeof f); cin>>K; for(int i=1;i<=K;i++)for(int j=1;j<=200;j++)G[i][j].clear(); for(int i=1;i<=K;i++)cin>>n[i]; for(int i=1;i<K;i++)cin>>m[i]; for(int i=1;i<K;i++){ for(int j=1,u,v;j<=m[i];j++)cin>>u>>v,G[i][u].emplace_back(v); } for(int i=1;i<=n[K];i++)f[K][i][i]=1; for(int i=K-1;i;i--){ for(int j=1;j<=n[i];j++)for(int ed=1;ed<=n[K];ed++){ for(int v:G[i][j])add(f[i][j][ed],f[i+1][v][ed]); } } for(int i=1;i<=n[1];i++)for(int j=1;j<=n[K];j++)a[i][j]=f[1][i][j]; int det=1; int z=n[1]; for(int i=1;i<=z;i++){ for(int j=i+1;j<=z;j++){ while(a[i][i]){ int d=a[j][i]/a[i][i]; for(int k=1;k<=z;k++)add(a[j][k],mod-1ll*a[i][k]*d%mod); swap(a[i],a[j]),det=-det; } swap(a[i],a[j]),det=-det; } } det=(det+mod)%mod; for(int i=1;i<=z;i++)det=1ll*det*a[i][i]%mod; cout<<det<<'\n'; } return 0; }
小插曲:当时看 OI-Wiki 怎么都看不懂,因为 OI-wiki 上是倒着写的,先写矩阵,再写 det,再写 =,再写 ,然后我就以为 LGV 引理是用来求某个矩阵的行列式的……
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】