【题解】洛谷 P6657 【模板】LGV 引理
wsyhb
·
·
题解
前言
为了使文章更加简洁,本文省略了有关 LGV 引理的定义,若有需要请前往 LGV 引理 - OI Wiki 以了解定义。
证明
先把 LGV 引理的式子写出来:
M
=
\begin{bmatrix}
e(A_1,B_1) & e(A_1,B_2) & \cdots & e(A_1,B_n) \\
e(A_2,B_1) & e(A_2,B_2) & \cdots & e(A_2,B_n) \\
\vdots & \vdots & \ddots & \vdots \\
e(A_n,B_1) & e(A_n,B_2) & \cdots & e(A_n,B_n)
\end{bmatrix}
\det(M)=\sum_{S:A \rightarrow B}(-1)^{N(\sigma(S))}\prod_{i=1}^{n}w(S_i)
即
\sum_{p}(-1)^{N(p)}\prod_{i=1}^ne(A_i,B_{p_{_i}})=\sum_{S:A \rightarrow B}(-1)^{N(\sigma(S))}\prod_{i=1}^{n}w(S_i)
(其中 p 为 1 到 n 的排列)
等式左边相当于选 n 条起点和终点分别互不相同的路径——设第 i 个起点对应的是第 p_i 个终点,若 p 逆序对个数为偶数则系数为 1,否则系数为 -1。
等式右边几乎完全一样,只是多了 n 条路径互不相交的限制。
因此,我们考虑存在路径相交的情况(此处路径相交不包括:起点或终点相同),可以发现:每一个偶数个逆序对的方案,与另一个奇数个逆序对的方案一一对应。
对应方法有很多种,此处举个例子:
对于一个存在路径相交的偶数个逆序对的方案,选出其中与其他路径相交的起点编号最小的路径 x,再选出与 x 相交的起点编号最小的路径 y,然后选出 x 和 y 的公共点中最靠近终点的点 p,将 p 到两个终点之间的部分互换,那么逆序对个数会变成奇数。
因此引理得证。
交换排列中的两个值,逆序对个数奇偶性会不同的证明:
设排列为 p,交换的位置编号为 x 和 y,其中 x<y,则 p_x,p_y 与另外某个数 p_i 贡献的逆序对个数有以下几种情况:
- 对于 i<x 或 i>y,p_i 与值 p_x,p_y 的相对位置在交换前后均未改变。
- 对于 x<i<y
- 若 p_i<p_x,p_y 或 p_i>p_x,p_y,则 p_i 与 p_x,p_y 贡献的逆序对个数在交换前后均为 1。
- 若 \min(p_x,p_y)<p_i<\max(p_x,p_y)
- 若 p_x<p_y,则 p_i 与 p_x,p_y 贡献的逆序对个数交换前后分别为 0,2。
- 若 p_x>p_y,则 p_i 与 p_x,p_y 贡献的逆序对个数交换前后分别为 2,0。
上述情况奇偶性均未变化,且 p_x 和 p_y 之间贡献的逆序对要么从无到有,要么从有到无,证毕。
题解
注意到条件 a_1 \le a_2 \le \cdots \le a_m 和 b_1 \le b_2 \le \cdots \le b_m,这说明只要路径不相交就一定有 (a_i,1) 对应的终点是 (b_i,n)。
因此所有不相交路径方案的系数恒为 1,直接套用 LGV 引理即可。
行列式的计算方法:高斯消元。
总时间复杂度 $O(n+m^3)$。
## 代码
``` cpp
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int max_n=1e6+5;
int fac[max_n<<1],inv[max_n<<1],inv_fac[max_n<<1],now=1;
const int max_m=100+5;
int a[max_m],b[max_m];
const int mod=998244353;
inline int qpow(int a,int n)
{
int res=1;
while(n)
{
if(n&1)
res=1ll*res*a%mod;
a=1ll*a*a%mod;
n>>=1;
}
return res;
}
inline int C(int n,int m)
{
return 1ll*fac[n]*inv_fac[m]%mod*inv_fac[n-m]%mod;
}
int M[max_m][max_m];
inline int det()
{
int res=1;
bool flag_neg=false;
for(int i=1;i<=m;++i)
{
int k=i;
while(k<=m&&!M[k][i])
++k;
if(k>m)
return 0;
if(k!=i)
{
for(int j=i;j<=m;++j)
swap(M[i][j],M[k][j]);
flag_neg^=1;
}
res=1ll*res*(M[i][i]+mod)%mod;
int t=qpow(M[i][i],mod-2);
for(int k=i+1;k<=m;++k)
{
int t0=1ll*t*M[k][i]%mod;
for(int j=i;j<=m;++j)
M[k][j]=(M[k][j]-1ll*M[i][j]*t0)%mod;
}
}
return flag_neg?mod-res:res;
}
int main()
{
fac[0]=inv_fac[0]=1;
fac[1]=inv_fac[1]=inv[1]=1;
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
while(now<2*n)
{
++now;
fac[now]=1ll*fac[now-1]*now%mod;
inv[now]=1ll*(mod-mod/now)*inv[mod%now]%mod;
inv_fac[now]=1ll*inv_fac[now-1]*inv[now]%mod;
}
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)
M[i][j]=a[i]<=b[j]?C(b[j]-a[i]+n-1,n-1):0;
printf("%d\n",det());
}
return 0;
}
```