【矩阵树定理+线代基础】HDU6420 Rikka with Spanning Tree

【前言】
当初多校的时候写了这题,后面改了两天愣是没过,于是坑了。
今天wyl在写矩阵树,突然想起来有这题,于是就过了。

【题目】
原题地址
给定一个长度为 n n n的序列 a i a_i ai,定义 b i b_i bi a a a的前缀和。现在有一幅 b n b_n bn个点的图,其中编号在 ( b i − 1 , b i ] (b_{i-1},b_i] (bi1,bi]之间的点两两有连边。除此之外还有 m m m条边,求这幅图的生成树个数。
n , m ≤ 200 , a i ≤ 1 0 6 n,m\leq 200,a_i\leq 10^6 n,m200,ai106

【解题思路】
一道需要一定线代基础的矩阵树定理题。

生成树个数我们肯定是考虑矩阵树定理的。由于块间连边只有 200 200 200条,而块个数也只有 200 200 200个,我们可以考虑只保留每个块的点以及那些有块间连边的点,然后矩阵树一波。于是我们先把这个行列式的形式写出来然后观察一下我们怎么能缩小这个行列式。

下面我们称与块外有连边的点为“关键点”,仅在块内连边的点为“自闭点”。
那么行列式大概是长这个样子的,其中 S i S_i Si表示第 i i i个块内部连边, 0 0 0的位置可能实际上有一些 − 1 -1 1.
[ S 1 0 0 0 0 S 2 0 0 0 0 ⋱ 0 0 0 0 S n ] \begin{bmatrix} S_1 & 0 & 0 & 0\\ 0 & S_2 & 0 & 0\\ 0 & 0 & \ddots & 0\\ 0 & 0 & 0 & S_n \end{bmatrix} S10000S200000000Sn
那么我们对于每个块的两行,交换这两行不会影响到其他块,我们就可以对每个块的形态进行考虑(以下设 n n n为块内点数, c 1 c_1 c1为关键点数, c 2 c_2 c2为自闭点个数,显然 n = c 1 + c 2 n=c_1+c_2 n=c1+c2

首先我们将所有点重标号,将关键点放到前面:
[ ? ? − 1 − 1 ⋯ − 1 ? ? − 1 − 1 ⋯ − 1 − 1 − 1 n − 1 − 1 ⋯ − 1 − 1 − 1 − 1 n − 1 ⋯ − 1 ⋮ ⋮ ⋮ ⋮ ⋱ − 1 − 1 − 1 − 1 − 1 − 1 n − 1 ] \begin{bmatrix} ? & ? & -1 & -1 & \cdots &-1\\ ? & ? & -1 & -1 &\cdots & -1\\ -1 & -1 & n-1 & -1 &\cdots & -1\\ -1& -1 & -1 & n-1 & \cdots &-1\\ \vdots & \vdots &\vdots &\vdots & \ddots &-1\\ -1 & -1 &-1 &-1 & -1 & n-1 \end{bmatrix} ??111??11111n111111n11111111n1
嗯,左上角一块我们暂时不用管它,反正最终我们是可以计算出来的。

发现自闭点一侧的行列式形式高度统一,于是我们让所有行都减去第 c 1 + 1 c_1+1 c1+1行的数,行列式值不变:
[ ? ? − n 0 ⋯ 0 ? ? − n 0 ⋯ 0 − 1 − 1 n − 1 − 1 ⋯ − 1 0 0 − n n ⋯ 0 ⋮ ⋮ ⋮ ⋮ ⋱ 0 0 0 − n 0 0 n ] \begin{bmatrix} ? & ? & -n & 0 & \cdots &0\\ ? & ? & -n & 0 &\cdots & 0\\ -1 & -1 & n-1 & -1 &\cdots & -1\\ 0& 0 & -n & n & \cdots &0\\ \vdots & \vdots &\vdots &\vdots & \ddots &0\\ 0 & 0 & -n &0 & 0 & n \end{bmatrix} ??100??100nnn1nn001n0000100n
接下来将第 c 1 + 2 ∼ n c_1+2\sim n c1+2n列加到第 c 1 + 1 c_1+1 c1+1列去,行列式值不变:
[ ? ? − n 0 ⋯ 0 ? ? − n 0 ⋯ 0 − 1 − 1 n − c 2 − 1 ⋯ − 1 0 0 0 n ⋯ 0 ⋮ ⋮ ⋮ ⋮ ⋱ 0 0 0 0 0 0 n ] \begin{bmatrix} ? & ? & -n & 0 & \cdots &0\\ ? & ? & -n & 0 &\cdots & 0\\ -1 & -1 & n-c_2 & -1 &\cdots & -1\\ 0& 0 & 0 & n & \cdots &0\\ \vdots & \vdots &\vdots &\vdots & \ddots &0\\ 0 & 0 & 0 &0 & 0 & n \end{bmatrix} ??100??100nnnc200001n0000100n
然后我们发现第 c 1 + 2 ∼ n c_1+2\sim n c1+2n行都只有一个地方有值,于是我们按行展开一波,最终可以得到这个东西:
n c 2 − 1 × [ ? ? − n ? ? − n − 1 − 1 n − c 2 ] n^{c_2-1}\times \begin{bmatrix} ? & ? & -n \\ ? & ? & -n \\ -1 & -1 & n-c_2 \\ \end{bmatrix} nc21×??1??1nnnc2
对每个块都这样做以后就可以得到一个 O ( n + 2 m ) O(n+2m) O(n+2m)的行列式了,然后消元一下就可以解决这道题了。细节十分多。
复杂度 O ( ( n + 2 m ) 3 ) O((n+2m)^3) O((n+2m)3)

【参考代码】

#include
#define pb push_back
#define mkp make_pair
#define fi first 
#define se second
using namespace std;

typedef long long ll;
typedef pair<int,int> pii;
const int mod=998244353,N=605;
int n,m,ans;
int siz[N],fa[N],a[N],pre[N];
pii u[N],v[N];
map<int,int>mp[N];

int read()
{
	int ret=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
	return ret;
}
void up(int &x,int y){x+=y;if(x>=mod)x-=mod;if(x<0)x+=mod;}

namespace DET
{
	int num[N][N];
	void initdet(){for(int i=0;i<=pre[n];++i)for(int j=0;j<=pre[n];++j) num[i][j]=0;}
	int qpow(int x,int y)
	{
		int res=1;
		for(;y;y>>=1,x=(ll)x*x%mod)if(y&1)res=(ll)res*x%mod;
		return res;
	}
	int gauss(int n)
	{
		--n;int res=1;
		for(int i=1;i<=n;++i)
		{
			int x=i;
			for(int j=i;j<=n;++j) if(num[j][i]) x=j;
			for(int j=1;j<=n;++j) swap(num[i][j],num[x][j]);
			if(x^i) res=-res;
			res=(ll)res*num[i][i]%mod;
			int inv=qpow(num[i][i],mod-2);
			for(int j=1;j<=n;++j) num[i][j]=(ll)num[i][j]*inv%mod;
			for(int j=i+1;j<=n;++j) if(num[j][i])
			{
				int t=num[j][i];
				for(int k=1;k<=n;++k) num[j][k]=(num[j][k]-(ll)num[i][k]*t)%mod;
			}
		}
		return (res+mod)%mod;
	}

}
using namespace DET;

namespace Graph
{
	pii newnode(int x,int y)
	{
		if(!mp[x][y]) ++siz[x],mp[x][y]=siz[x];
		return mkp(x,mp[x][y]);
	}
	pii getpair()
	{
		int x=read();
		for(int i=1;i<=n;x-=a[i],++i) if(x<=a[i]) return newnode(i,x);
	}
	int getid(int x,int y)
	{
		if(a[x]==siz[x]) return pre[x-1]+y;
		return pre[x-1]+y+1;
	}
	void addedge(pii x,pii y)
	{
		//printf("%d %d %d %d\n",x.fi,x.se,y.fi,y.se);
		int t1=getid(x.fi,x.se),t2=getid(y.fi,y.se);
		up(num[t1][t1],1);up(num[t1][t2],-1);
		up(num[t2][t2],1);up(num[t2][t1],-1);
	}
	int findf(int x){return fa[x]==x?x:fa[x]=findf(fa[x]);}
}
using namespace Graph;

namespace DreamLolita
{
	void readin()
	{
		n=read();m=read();
		for(int i=1;i<=n;++i) a[i]=read(),mp[i].clear(),siz[i]=0,fa[i]=i;
		for(int i=1;i<=m;++i) u[i]=getpair(),v[i]=getpair();
		for(int i=1;i<=m;++i) fa[findf(u[i].fi)]=findf(v[i].fi);	
	}
	void getdet()
	{
		pre[0]=0;ans=1;
		for(int i=1;i<=n;++i) 
		{	
			if(siz[i]==a[i]) pre[i]=pre[i-1]+siz[i];
			else pre[i]=pre[i-1]+siz[i]+1,ans=(ll)ans*qpow(a[i],a[i]-siz[i]-1)%mod;
		}
		initdet();
		for(int i=1;i<=n;++i) 
		{
			if(siz[i]==a[i])
			{
				for(int j=1;j<=a[i];++j) for(int k=j+1;k<=a[i];++k) 
					addedge(mkp(i,j),mkp(i,k));
			}
			else
			{
				int t=getid(i,0);
				for(int j=1;j<=siz[i];++j)
				{
					int x=getid(i,j);
					up(num[x][t],-a[i]);up(num[x][x],a[i]);up(num[t][x],-1);
				}
				up(num[t][t],siz[i]);
			}
		}
		for(int i=1;i<=m;++i) addedge(u[i],v[i]);
	}
	void solve()
	{
		readin();
		if(!m)
		{
			if(n==1) printf("%d\n",qpow(a[1],a[1]-2)); else puts("0");
			return;
		}
		if(n>1){for(int i=1;i<=n;++i) if(!siz[i]) {puts("0");return;}}
		getdet();
		ans=(ll)ans*gauss(pre[n])%mod;
		printf("%d\n",ans);
	}
}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("HDU6420.in","r",stdin);
	freopen("HDU6420.out","w",stdout);
#endif
	int T=read();
	while(T--) DreamLolita::solve();
	return 0;
}

你可能感兴趣的:(其他-矩阵树定理,数论-高斯消元)