题目
解法
首先考虑 (k=2) 的情况。这相当于枚举全排列 (p),每种排列的逆序对数就是交点数。令 ( au(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) 个点,再按照之前的方法计算。这时就要引出一个公式 ——
( ext{Binet-Cauchy}) 公式:
设 (A=(a_{i,j})_{n imes m},B=(b_{i,j})_{m imes n})。
(n>m)。就有 ( ext{det}(AB)=0)。
( ext{rank}(AB)le ext{rank}(A)le m<n)。所以矩阵 (AB) 不满秩。
(nle m)。(AB) 的行列式等于 (A) 的所有 (n) 阶子式以及 (B) 相应 (n) 阶子式乘积之和。
题目中保证 (nle 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(),'
'); A.Clear();
}
return 0;
}