啊。

 

 

题目

传送门

解法

首先考虑 \(k=2\) 的情况。这相当于枚举全排列 \(p\),每种排列的逆序对数就是交点数。令 \(\tau(p)\) 是某排列逆序对数,\(g\) 是邻接矩阵,将其公式化就是:

 

\[\text{Ans}=\sum_{p}(-1)^{\tau(p)}\prod_{i=1}^n g_{i,p_i} \]

 

这不就是 \(g\) 的行列式吗?这个可以用高斯消元 \(\mathcal O(n^3)\) 求出。

对于 \(k=3\) 且 \(n_1=n_2=n_3\) 的情况。我们发现,它的答案就是 \(g_1,g_2\) 行列式的乘积。设 \(f_{i,0/1}\) 为 \(i\) 到 \(i+1\) 层交点个数为偶/奇的方案数。那么有:

 

\[\text{det}(g_1)\text{det}(g_2)=(f_{1,0}-f_{1,1})(f_{2,0}-f_{2,1}) \]

 

 

\[=(f_{1,0}f_{2,0}+f_{1,1}f_{2,1})-(f_{1,0}f_{2,1}+f_{1,1}f_{2,0}) \]

 

其它情况可以归纳证明。

但是 \(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;
}