【题解】洛谷 P6657 【模板】LGV 引理

· · 题解

前言

为了使文章更加简洁,本文省略了有关 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)

(其中 p1n 的排列)

等式左边相当于选 n 条起点和终点分别互不相同的路径——设第 i 个起点对应的是第 p_i 个终点,若 p 逆序对个数为偶数则系数为 1,否则系数为 -1

等式右边几乎完全一样,只是多了 n 条路径互不相交的限制。

因此,我们考虑存在路径相交的情况(此处路径相交不包括:起点或终点相同),可以发现:每一个偶数个逆序对的方案,与另一个奇数个逆序对的方案一一对应

对应方法有很多种,此处举个例子:

对于一个存在路径相交的偶数个逆序对的方案,选出其中与其他路径相交的起点编号最小的路径 x,再选出与 x 相交的起点编号最小的路径 y,然后选出 xy 的公共点中最靠近终点的点 p,将 p 到两个终点之间的部分互换,那么逆序对个数会变成奇数。

因此引理得证。

交换排列中的两个值,逆序对个数奇偶性会不同的证明:

设排列为 p,交换的位置编号为 xy,其中 x<y,则 p_x,p_y 与另外某个数 p_i 贡献的逆序对个数有以下几种情况:

上述情况奇偶性均未变化,且 p_xp_y 之间贡献的逆序对要么从无到有,要么从有到无,证毕。

题解

注意到条件 a_1 \le a_2 \le \cdots \le a_mb_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; } ```