【洛谷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!
}
待到再迷茫时回头望,所有脚印会发出光芒