[NOI2021] 路径交点

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

一、题目

点此看题

二、解法

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

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

  • 选取的路径数正好是 (n_1),它并不是一个任意的数,而是求的一个几乎选满的方案,如果你做过这题就会知道这种特殊的组合问题可能会适用比内(-)柯西公式。
  • 本题求的方案是偶数交点的方案减去奇数交点的方案,那么一种方案的贡献是 ((-1)^{x}),再观察交点的定义式 ((p_j-q_j) imes(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 imes 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
",gauss(n[1],A.a));
}
signed main()
{
	T=read();
	while(T--) work();
}
原文地址:https://www.cnblogs.com/C202044zxy/p/15063251.html