把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷5296】[北京省选集训2019] 生成树计数(矩阵树定理)

点此看题面

  • 给定一张\(n\)个点的无向完全图,每条边有一个边权。求所有生成树边权和的\(k\)次方之和。
  • \(n,k\le30\)

矩阵树定理

考虑我们矩阵树定理维护的是所有生成树边权之积的和,因此一般遇到这种问题会去想把边权设成一个多项式。

假设我们分别维护出了两块元素的前\(k\)次方和(记作\(A_{0\sim k}\)\(B_{0\sim k}\)),那么根据二项式定理展开则有:

\[F_i=\sum_{j=0}^iC_i^jA_jB_{i-j} \]

套路地暴拆组合数:

\[\frac{F_i}{i!}=\sum_{j=0}^i\frac{A_j}{j!}\times\frac{B_{i-j}}{(i-j)!} \]

也就是说这相当于是两个指数型生成函数做卷积的形式。

那么我们只要最初把权值为\(w\)的边的边权设成\(\sum_{i=0}^k\frac{w^i}{i!}x^i\),然后套用矩阵树定理即可。

最后答案就是解得多项式的第\(k\)项乘上\(k!\)(因为指数型生成函数中除去了\(k!\))。

代码:\(O(n^3k^2)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 30
#define X 998244353
using namespace std;
int n,k,Fac[N+5],IFac[N+5];
I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
struct F
{
	int f[N+5];I F() {for(RI i=0;i<=k;++i) f[i]=0;}
	I int& operator [] (CI x) {return f[x];}I int operator [] (CI x) Con {return f[x];}
	I F operator + (Con F& g) Con {F t;for(RI i=0;i<=k;++i) t[i]=(f[i]+g[i])%X;return t;}//多项式加法
	I F operator - (Con F& g) Con {F t;for(RI i=0;i<=k;++i) t[i]=(f[i]-g[i]+X)%X;return t;}//多项式减法
	I F operator * (Con F& g) Con//多项式乘法
	{
		F t;for(RI i=0;i<=k;++i) for(RI j=0;j<=k-i;++j) t[i+j]=(t[i+j]+1LL*f[i]*g[j])%X;return t;
	}
	I F Inv() Con//多项式求逆
	{
		F t;for(RI i=0,j,x;i<=k;++i) {for(x=j=0;j^i;++j)
			x=(x+1LL*t[j]*f[i-j])%X;t[i]=1LL*(!i-x+X)*QP(f[0],X-2)%X;}return t;
	}
	I friend bool operator ! (Con F& o) {for(RI i=0;i<=k;++i) if(o[i]) return 0;return 1;}//判是否全为0
	I friend F operator - (Con F& o) {F t;for(RI i=0;i<=k;++i) t[i]=o[i]?X-o[i]:0;return t;}//所有系数取负
}f,a[N+5][N+5];
I F Gauss(CI n)//高斯消元求解行列式
{
	F t,s;s[0]=1;for(RI i=1,j,k;i<=n;s=s*a[i][i],++i)
	{
		if(!a[i][i]) {for(s=-s,j=i+1;j<=n&&!a[j][i];++j);for(k=i;k<=n;++k) swap(a[i][k],a[j][k]);}
		for(j=i+1;j<=n;++j) for(t=-a[j][i]*a[i][i].Inv(),k=i;k<=n;++k) a[j][k]=a[j][k]+t*a[i][k];
	}return s;
}
int main()
{
	RI i,j;for(scanf("%d%d",&n,&k),Fac[0]=i=1;i<=k;++i) Fac[i]=1LL*Fac[i-1]*i%X;
	for(IFac[i=k]=QP(Fac[k],X-2);i;--i) IFac[i-1]=1LL*IFac[i]*i%X;
	RI x,p,t;for(i=1;i<=n;++i) for(j=1;j<=n;++j)
	{
		for(scanf("%d",&x),p=0,t=1;p<=k;++p,t=1LL*t*x%X) f[p]=1LL*t*IFac[p]%X;//列出多项式
		a[i][i]=a[i][i]+f,a[i][j]=a[i][j]-f;//矩阵树定理:度数矩阵-邻接矩阵
	}
	return printf("%d\n",1LL*Fac[k]*Gauss(n-1)[k]%X),0;//最后答案要乘上k!
}
posted @ 2021-05-12 11:21  TheLostWeak  阅读(62)  评论(0编辑  收藏  举报