题目 解法
首先考虑 \(k=2\) 的情况。这相当于枚举全排列 \(p\),每种排列的逆序对数就是交点数。令 \(\tau(p)\) 是某排列逆序对数,\(g\) 是邻接矩阵,将其公式化就是:
这不就是 \(g\) 的行列式吗?这个可以用高斯消元 \(\mathcal O(n^3)\) 求出。
对于 \(k=3\) 且 \(n_1=n_2=n_3\) 的情况。我们发现,它的答案就是 \(g_1,g_2\) 行列式的乘积。设 \(f_{i,0/1}\) 为 \(i\) 到 \(i+1\) 层交点个数为偶/奇的方案数。那么有:
其它情况可以归纳证明。
但是 \(n_i\) 并没有那么理想。还是考虑 \(k=3\) 且 \(n_1=n_3< n_2\) 的情况。我们可以枚举第 \(2\) 层选了哪 \(n_1\) 个点,再按照之前的方法计算。这时就要引出一个公式 ——
\(\text{Binet-Cauchy}\) 公式:
设 \(A=(a_{i,j})_{n\times m},B=(b_{i,j})_{m\times n}\)。
\(n>m\)。就有 \(\text{det}(AB)=0\)。
\(\text{rank}(AB)\le \text{rank}(A)\le m<n\)。所以矩阵 \(AB\) 不满秩。
\(n\le m\)。\(AB\) 的行列式等于 \(A\) 的所有 \(n\) 阶子式以及 \(B\) 相应 \(n\) 阶子式乘积之和。
题目中保证 \(n\le m\),于是我们可以直接用此公式。也即所有邻接矩阵的乘积的行列式就是答案。
你还可以用另一种方式理解这个答案。
注意到,答案只和第 \(1\) 层到达第 \(k\) 层的邻接矩阵有关。因为初末相对位置决定了路径 \(i\) 是否会穿过路径 \(j\)(我们认为穿过去再穿回来是无效的)。
不过还有个问题:不论是用公式还是以上文的理解,我们都没有考虑 每个顶点至多出现在一条路径中 的要求。事实上,假设第 \(1\) 层的 \(i,j\) 都经过了某层的点 \(o\),在 \(o\) 之后 \(i,j\) 的可选方案实际上是一样的。若 \(i\) 最终到达第 \(n\) 层的 \(i'\),\(j\) 到了 \(j'\),它可以和 \(i\) 到 \(j'\),\(j\) 到 \(i'\) 的情况相互抵消(逆序对数只变化 \(1\)),而这些情况可以两两配对,所以对答案没有影响。
总时间复杂度应该是 \(\mathcal O(n^3kT)\) 的。
再提一嘴如果直接计算路径方案数就是积和式,它的计算需要 \(\mathcal O(n!)\)。所以加上这个 \(\mathtt{qqgg}\) 的奇偶限制题目变简单了。
代码#include <cstdio>
#define print(x,y) write(x),putchar(y)
template <class T> inline T read(const T sample) {
T x=0; char s; bool f=0;
while((s=getchar())>'9' or s<'0')
f|=(s=='-');
while(s>='0' and s<='9')
x=(x<<1)+(x<<3)+(s^48),
s=getchar();
return f?-x:x;
}
template <class T> inline void write(const T x) {
if(x<0) return (void)(putchar('-'),write(-x));
if(x>9) write(x/10);
putchar(x%10^48);
}
#include <cstring>
#include <iostream>
using namespace std;
const int mod=998244353,maxk=105;
inline int inc(const int x,const int y) {
return x+y>=mod?x+y-mod:x+y;
}
int inv(int x,int y=mod-2) {
int r=1;
while(y) {
if(y&1) r=1ll*r*x%mod;
x=1ll*x*x%mod; y>>=1;
}
return r;
}
int k,n[maxk],m[maxk];
struct mat {
int a[maxk<<1][maxk<<1],n,m;
mat operator * (const mat &t) const {
mat r; r.n=n,r.m=t.m;
memset(r.a,0,sizeof r.a);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
if(a[i][j])
for(int k=1;k<=r.m;++k)
r.a[i][k]=inc(
r.a[i][k],
1ll*a[i][j]*t.a[j][k]%mod
);
return r;
}
void Clear() {
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
a[i][j]=0;
}
int Gauss() {
int j,tmp,r=1; bool f=0;
for(int i=1;i<=n;++i) {
for(j=i;j<=n;++j)
if(a[j][i]) break;
if(j>n) return 0;
if(i^j) swap(a[i],a[j]),f^=1;
r=1ll*r*a[i][i]%mod;
tmp=inv(a[i][i]);
for(j=i;j<=n;++j)
a[i][j]=1ll*a[i][j]*tmp%mod;
for(j=i+1;j<=n;++j) {
a[j][i]=mod-a[j][i];
for(int k=i+1;k<=n;++k)
a[j][k]=inc(a[j][k],1ll*a[j][i]*a[i][k]%mod);
a[j][i]=0;
}
}
return f?mod-r:r;
}
} A,B,C;
signed main() {
int u,v;
for(int T=read(9);T;--T) {
k=read(9);
for(int i=1;i<=k;++i)
n[i]=read(9);
for(int i=1;i<k;++i)
m[i]=read(9);
A.n=n[1],A.m=n[2];
for(int i=1;i<=m[1];++i) {
u=read(9),v=read(9);
A.a[u][v]=1;
}
for(int i=2;i<k;++i) {
B.n=n[i],B.m=n[i+1];
for(int j=1;j<=m[i];++j) {
u=read(9),v=read(9);
B.a[u][v]=1;
}
C=A*B;
A=C,B.Clear();
}
print(A.Gauss(),'\n'); A.Clear();
}
return 0;
}