[NOI2021] 路径交点

没去 \(\tt NOI\) 现场的菜鸡\(+\)新高二退役狗来写一波题解。

一、题目

点此看题

二、解法

前置知识:行列式,矩阵乘法,高斯消元,比内\(-\)柯西公式。

你看这题真是奇怪得不行,我以前做过这种题吗?没关系,我们来搞一些 \(\tt observation\)

  • 选取的路径数正好是 \(n_1\),它并不是一个任意的数,而是求的一个几乎选满的方案,如果你做过这题就会知道这种特殊的组合问题可能会适用比内\(-\)柯西公式。
  • 本题求的方案是偶数交点的方案减去奇数交点的方案,那么一种方案的贡献是 \((-1)^{x}\),再观察交点的定义式 \((p_j-q_j)\times(p_{j+1}-q_{j+1})<0\),明显是逆序对个数啊,那么可以自然的联想到行列式。

那么我们就从行列式的角度来解决这道题吧,首先考虑 \(k=2\) 的情况,设 \(p_i\) 表示 \(X\) 部第 \(i\) 个点匹配的 \(Y\) 部点是 \(p_i\),设 \(f(p)\) 表示排列 \(p\) 的逆序对个数,设 \(g_{i,j}\) 表示 \((i,j)\) 是有边,我们通过枚举 \(p\) 可以暴力写出答案式:

\[\sum_{p}(-1)^{f(p)}\prod_{i=1}^{n_1} g_{i,p_i} \]

那么不难发现就是矩阵 \(g_{i,j}\) 的行列式,因为我们已经会求二分图的情况,那么分层图的情况我们考虑把每一层一个一个合并上去,归约到二分图的情况,看图:

所以比内\(-\)柯西公式帮助我们完成了从中间这一层 \(m\) 个点中选出 \(n\) 个的任务,选出点后两边可以分别匹配因为满足乘法原理,行列式也可以直接相乘。那么合并操作就相当于把邻接矩阵做矩阵乘法,那么算法流程是我们把每一层的邻接矩阵暴力乘起来,最后对 \(n\times n\) 的矩阵高斯消元,所得行列式即是答案,时间复杂度 \(O(n^4)\)

三、总结

某位巨佬说:像这种偶数减奇数的题正好符合行列式模型,所以往那方面想。

本题讨论二部图的行列式求法个人觉得很难,但和 \(dp\) 做法一样,都是从枚举到优化的过程,但你不去在思想上枚举是想不通的。分层图问题转化为二部图问题用到了归约的思想,值得借鉴!

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 205;
const int MOD = 998244353;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int T,n[M],m[M],k;
struct mat
{
	int n,m,a[M][M];
	void clear() {n=m=0;memset(a,0,sizeof a);}
	mat() {clear();}
	mat operator * (const mat &b) const
	{
		mat r;
		r.n=n;r.m=b.m;
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
				for(int k=1;k<=b.m;k++)
					r.a[i][k]=(r.a[i][k]+a[i][j]*b.a[j][k])%MOD;
		return r;
	}
}A,B;
int qkpow(int a,int b)
{
	int r=1;
	while(b>0)
	{
		if(b&1) r=r*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return r;
}
int gauss(int n,int a[][M])
{
	int ans=1;
	for(int i=1;i<=n;i++)
	{
		for(int j=i;j<=n;j++)
			if(!a[i][i] && a[j][i])
			{
				swap(a[i],a[j]);
				ans*=-1;
				break;
			}
		ans=ans*a[i][i]%MOD;
		for(int j=1;j<=n;j++)
		{
			if(i==j || !a[j][i]) continue;
			int t=a[j][i]*qkpow(a[i][i],MOD-2)%MOD;
			for(int k=1;k<=n;k++)
				a[j][k]=(a[j][k]-a[i][k]*t)%MOD;
		}
	}
	return (ans%MOD+MOD)%MOD;
}
void work()
{
	k=read();
	for(int i=1;i<=k;i++) n[i]=read();
	for(int i=1;i<k;i++) m[i]=read();
	A.clear();A.n=n[1];A.m=n[2];
	for(int i=1;i<=m[1];i++)
	{
		int u=read(),v=read();
		A.a[u][v]=1;
	}
	for(int t=2;t<k;t++)
	{
		B.clear();
		B.n=n[t];B.m=n[t+1];
		for(int i=1;i<=m[t];i++)
		{
			int u=read(),v=read();
			B.a[u][v]=1;
		}
		A=A*B;
	}
	printf("%lld\n",gauss(n[1],A.a));
}
signed main()
{
	T=read();
	while(T--) work();
}
posted @ 2021-07-26 20:38  C202044zxy  阅读(127)  评论(0编辑  收藏  举报