[NOI 2021] 路径交点

题目

传送门

解法

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

[ ext{Ans}=sum_{p}(-1)^{ au(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) 层交点个数为偶/奇的方案数。那么有:

[ ext{det}(g_1) ext{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) 个点,再按照之前的方法计算。这时就要引出一个公式 ——

( 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;
}
原文地址:https://www.cnblogs.com/AWhiteWall/p/15065175.html