定理内容
对于一条路径 P,设 ω(P)=i∈edge of P∏wi,其中 w 是边权,e(x,y)=∀P:x→y∑ω(P)。
对于计数问题边权可以设置为 1,对于轮廓线 DP 其实边权也可以是一些奇奇怪怪的函数。
有起点集合 A 和终点集合 B,那么假设有一个排列 σ,路径 Pi 就是 Ai→Bσ(i)。
最后,设 t(σ) 为 σ 的逆序对数量,LGV 引理证明的就是下面的方阵 M 满足:
Det(M)=∀S:A→B∑(−1)t(σ)i=1∏nω(Si)
M=∣∣∣∣∣∣∣∣∣e(A1,B1)e(A2,B1)⋮e(An,B1)e(A1,B2)e(A1,B3)⋮e(An,B2)⋯⋯⋱⋯e(A1,Bn)e(A2,Bn)⋮e(An,Bn)∣∣∣∣∣∣∣∣∣
证明
其中 S 表示表示从 A 到 B 所有的不相交的路径 P。
首先考虑行列式的定义,即对于一个 n×n 的方阵 A,其行列式为:
Det(M)=∀σ∑(−1)t(σ)i=1∏nai,σ(i)
其中 σ 是一个 1 到 n 的排列。
具体到上面的矩阵,显然其行列式为:
Det(M)=∀σ∑(−1)t(σ)i=1∏ne(Ai,Bσ(i))
根据 e(x,y) 的定义,其显然可以转化为:
Det(M)=∀σ∑(−1)t(σ)i=1∏n∀P:Ai→Bσ(i)∑ω(P)
根据乘法原理,上式的 i=1∏n∀P:Ai→Bσ(i)∑ω(P) 就相当求解的是在所有的 ∀i∈[1,n]∩Z∣Ai→Bσ(i) 的路径中,所有可能的路径的组 P 的 i=1∑nω(Pi) 的加和。
所以就可以继续化简得到:
Det(M)=∀σ∑(−1)t(σ)P=σ∑ω(P)
还是根据乘法原理,有可以得到:
Det(M)=∀P:A→B∑(−1)t(σ)i=1∏nω(Pi)
观察上式与定理的区别,发现现在仅需证明对于所有有交的路径 G 的贡献均为 0 就可以证明原定里了。
要证明原定理成立,首先需要证明对于一个排列 σ 满足交换 i,j∈[1,n]∩Z∧i=j 一定会使 σ 逆序对的奇偶性改变。
先证明交换相邻的元素序列的奇偶性会改变,这十分显然因为如果交换 Ai,Ai+1 那么因为没有相同的元素所以 Ai,Ai+1 之间必定会贡献一个逆序对的变化,而对于 [1,i) 和 (i+1,n] 的贡献都是一样的,所以 t(σ) 的奇偶性必然改变。考虑更一般的情况,假设只能交换相邻的元素,交换 Ai 与 Aj 的贡献就是交换 Ai,Aj−1 的贡献 +2(一开始把 Aj 换到 Aj−1 最后再换回来)。
我们可以一直将距离不断缩小直到 j=i+1,因为我们已经证明了交换 Ai,Ai+1 会改变奇偶性所以要自然可以证明一个排列任意交换两个元素其逆序对的奇偶性偶会改变。
回到 LGV 引理的证明。
对于一个有交点的路径 Ai→Bi,Aj→Bj,假设其第一次相交于 u,也就是其路径形如 Ai→u→Bi 和 Aj→u→Bj。这时候如果交换 σ(i) 和 σ(j) 那么就会发现另外一条相交的路径 Ai→u→Bj 和 Aj→u→Bi。
显然两条路径 P,P′ 满足 ω(P)=ω(P′) 且因为上面的关于奇偶性的证明,有 (−1)t(σ)+(−1)t(σ′)。
所以自然有结论:
∀G:A→B∑(−1)t(σ)i=1∏nω(Gi)=0
也就得到了:
Det(M)=∀S:A→B∑(−1)t(σ)i=1∏nω(Si)
本题的求解
因为这个题目保证 1≤a1≤a2≤⋯≤am≤n,1≤b1≤b2≤⋯≤bm≤n,所以如果起点与终点并不是一一对应的,那么就一定会出现交点,也就是说有贡献的 σ 都满足 σ(i)=i。
对于 e(Ai,Bj) 使用组合数求解,直接输出 Det(M) 就可以了,时间复杂度 O(n3)。
#include<iostream>
#define int long long
using namespace std;
const int N=5e6+5,M=505,mod=998244353;
int ksm(int a,int b){
int ans=1;
while(b){
if(b&1){
ans=ans*a%mod;
}
b>>=1;
a=a*a%mod;
}
return ans;
}
int n,m,a[M],b[M],inv[N],jc[N],f[M][M];
int C(int n,int m){
if(n<m){
return 0;
}
return jc[n]*inv[m]%mod*inv[n-m]%mod;
}
void solve(){
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++){
f[i][j]=C(b[j]-a[i]+n-1,n-1);
}
}
int flag=1;
for(int i=1;i<=m;i++){
for(int j=i+1;j<=m;j++){
while(f[i][i]){
int s=f[j][i]/f[i][i];
for(int k=i;k<=m;k++){
f[j][k]=(f[j][k]-s*f[i][k]+mod)%mod;
}
flag++;
swap(f[i],f[j]);
}
flag++;
swap(f[i],f[j]);
}
}
int ans=1;
for(int i=1;i<=m;i++){
ans=(ans*f[i][i])%mod;
}
cout<<(ans*(flag%2?1:-1)+mod)%mod<<'\n';
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
jc[0]=1;
for(int i=1;i<N;i++){
jc[i]=jc[i-1]*i%mod;
}
inv[N-1]=ksm(jc[N-1],mod-2);
for(int i=N-1;i>=1;i--){
inv[i-1]=inv[i]*i%mod;
}
int T;cin>>T;
while(T--) solve();
return 0;
}