【数学】LGV 引理
题目描述
这是一道模板题。
有一个
现在有
两种方案不同当且仅当存在至少一个棋子所经过的点不同。
算法描述
LGV 引理,用于解决 DAG 上一个起点集合到一个终点集合不交路径的统计问题。
下面给出定义:
首先有一个排列
定义
引理:
也就是这个矩阵的行列式等于所有
证明不会。
也就是说,右边这个所有路径集合的抽象统计问题被转化成了左边这个东西的行列式,而左边的东西只需要求出一对点之间所有路径的权值和即可,有些时候这是可求的。
行列式怎么求自行搜索。
但是由于右边这个排列的逆序对数次方太过抽象,通常我们只用弱化版的结论,比如说这道模板题里面只有
对于本题,也就是计数问题,我们发现一条路径贡献
至于
注意起点数量要和终点数量相等。
套 LGV 模板即可。
#include<bits/stdc++.h>
using namespace std;
const int N = 2e6 + 5,M = 105,MOD = 998244353;
typedef long long ll;
ll frac[N],inv[N];
int n,m,a[M],b[M],c[M][M];
inline ll C(int y,int x)
{
if(y < 0 || x < 0 || y < x) return 0;
return frac[y] * inv[x] % MOD * inv[y - x] % MOD;
}
inline ll ksm(ll base,int pts)
{
ll ret = 1;
for(;pts > 0;pts >>= 1,base = base * base % MOD)
if(pts & 1)
ret = ret * base % MOD;
return ret;
}
inline ll getdet()
{
ll ret = 1;
for(int i = 1;i <= m;i++)
{
int tmp = i;
while(tmp <= m && c[tmp][i] == 0) ++tmp;
if(tmp == m + 1) return 0ll;
swap(c[tmp],c[i]);
if(tmp ^ i) ret = MOD - ret;
ret = ret * c[i][i] % MOD;
for(int j = i + 1;j <= m;j++)
{
ll rate = c[j][i] * ksm(c[i][i],MOD - 2) % MOD;
for(int k = 1;k <= m;k++) c[j][k] = (c[j][k] - c[i][k] * rate % MOD + MOD) % MOD;
}
}
return ret;
}
int main()
{
frac[0] = inv[0] = 1;
for(int i = 1;i < N;i++) frac[i] = frac[i - 1] * i % MOD;
inv[N - 1] = ksm(frac[N - 1],MOD - 2);
for(int i = N - 2;i >= 1;i--) inv[i] = inv[i + 1] * (i + 1) % MOD;
int T;
cin>>T;
while(T--)
{
cin>>n>>m;
for(int i = 1;i <= m;i++) cin>>a[i]>>b[i];
for(int i = 1;i <= m;i++)
for(int j = 1;j <= m;j++)
{
if(a[i] > b[j]) c[i][j] = 0;
else c[i][j] = C(n - 1 + b[j] - a[i],b[j] - a[i]);
}
ll ans = getdet();
cout<<ans<<endl;
}
return 0;
}
本人自认为下面这道题更好地利用了 LGV 引理的性质:
题面太长,不在这里挂。
考虑要求从第
没法做诶,起终点关系又没法直接和中间的交点数量对应...
再读一边题,发现求的是奇偶性个数统计。
观察起终点位置关系对奇偶性的影响,发现一旦
否则一定是偶数个。(自己画图研究可以发现)
所以 LGV 引理中 "逆序对数量奇偶性" 就派上了作用,容易发现逆序对数量奇偶性,就是交点数量奇偶性。
所以 LGV 引理即可,至于
#include<bits/stdc++.h>
using namespace std;
const int N = 205,MOD = 998244353;
typedef long long ll;
ll c[N][N];
int k,n[N],m[N],w[N][N][N],num[N][N];
inline ll ksm(ll base,int pts)
{
ll ret = 1;
for(;pts > 0;pts >>= 1,base = base * base % MOD)
if(pts & 1)
ret = ret * base % MOD;
return ret;
}
inline void bfs(int S)
{
queue <pair<int,int> > q;
q.push(make_pair(1,S));
num[1][S] = 1;
while(!q.empty())
{
int x = q.front().second,d = q.front().first; q.pop();
if(d == k) continue;
for(int i = 1;i <= n[d + 1];i++)
if(w[d][x][i])
{
if(!num[d + 1][i]) q.push(make_pair(d + 1,i));
num[d + 1][i] = (num[d + 1][i] + num[d][x]) % MOD;
}
}
}
inline ll getdet()
{
ll ret = 1;
for(int i = 1;i <= n[1];i++)
{
int tmp = i;
while(tmp <= n[1] && c[tmp][i] == 0) ++tmp;
if(tmp == n[1] + 1) return 0ll;
for(int j = 1;j <= n[1];j++) swap(c[tmp][j],c[i][j]);
if(tmp ^ i) ret = MOD - ret;
ret = ret * c[i][i] % MOD;
for(int j = i + 1;j <= n[1];j++)
{
ll rate = c[j][i] * ksm(c[i][i],MOD - 2) % MOD;
for(int k = 1;k <= n[1];k++) c[j][k] = (c[j][k] - c[i][k] * rate % MOD + MOD) % MOD;
}
}
return ret;
}
int main()
{
int T;
cin>>T;
while(T--)
{
memset(w,0,sizeof(w));
cin>>k;
for(int i = 1;i <= k;i++) cin>>n[i];
for(int i = 1;i <= k - 1;i++) cin>>m[i];
for(int i = 1;i <= k - 1;i++)
for(int j = 1,x,y;j <= m[i];j++)
{
cin>>x>>y;
w[i][x][y] = 1;
}
for(int i = 1;i <= n[1];i++)
{
memset(num,0,sizeof(num));
bfs(i);
for(int j = 1;j <= n[k];j++) c[i][j] = num[k][j];
}
ll res = getdet();
cout<<res<<endl;
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】