CF917D Stranger Trees
Link
Description
给定一棵有标号的树,求恰好与该树有 \(k\) 条边相同的树的个数。分别对 \(k\in[0,n)\) 求值。
Solution
主要考察对矩阵树定理的理解程度。我们知道由矩阵树定理求得的矩阵的行列式的值是该图所有生成树中边权乘积的和,即
\[\sum_{T}\prod_{e\in T} val_e
\]
如果把给定树的树边标成 \(x\),非树边标成 \(1\),那么一个生成树的所有边的权值积就是一个单项式 \(1^p x^{n-1-p}\),其中 \(x\) 的指数就蕴含了有多少条边是给定树的树边。通过这个很巧妙的方法,对所有这样的单项式求和,就得到一个多项式,其中 \(x^k\) 的系数就表示恰好有 \(k\) 条边相同的生成树个数。我们又知道一个 \(n-1\) 次多项式只需要 \(n\) 个点值就能被确定,所以分别令 \(x=1\dots n\) 跑矩阵树定理,得到一个线性方程组。最后再高斯消元一下,就能得到多项式系数,即答案。
#include<stdio.h>
#include<algorithm>
using namespace std;
#define N 107
#define Mod 1000000007
#define ll long long
int n;
int X[N],Y[N];
ll a[N][N],A[N][N],ans[N];
ll qpow(ll x,ll y){
ll ret=1,cnt=0;
while(y>=(1<<cnt)){
if(y&(1<<cnt)) ret=ret*x%Mod;
x=x*x%Mod,cnt++;
}
return ret;
}
void build(){
for(int i=1;i<=n;i++) a[i][i]=n-1;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i!=j) a[i][j]=Mod-1;
}
int main(){
scanf("%d",&n);
for(int i=1;i<n;i++)
scanf("%d%d",&X[i],&Y[i]);
for(int x=1;x<=n;x++){
build();
for(int i=1;i<n;i++){
int u=X[i],v=Y[i];
a[u][u]+=x-1,a[v][v]+=x-1;
a[u][v]=a[v][u]=Mod-x;
}
int m=n-1; ll ans=1,op=1;
for(int i=1;i<=m;i++){
int p=i;
for(;p<=n;p++) if(a[p][i]) break;
if(p!=i) swap(a[p],a[i]),op=-op;
ll inv=qpow(a[i][i],Mod-2);
for(int j=i+1;j<=n;j++){
ll mul=a[j][i]*inv%Mod;
for(int k=i;k<=n;k++)
a[j][k]=(a[j][k]-a[i][k]*mul%Mod+Mod)%Mod;
}
ans=(ans*a[i][i]%Mod+Mod)%Mod;
}
A[x][n+1]=(ans*op%Mod+Mod)%Mod; A[x][1]=1;
for(int i=2;i<=n;i++) A[x][i]=A[x][i-1]*x%Mod;
}
for(int i=1;i<=n;i++){
int p=0;
for(p=i;p<n;p++) if(A[p][i]) break;
if(p!=i) swap(A[i],A[p]);
ll Inv=qpow(A[i][i],Mod-2);
for(int j=n+1;j>=i;j--) A[i][j]=A[i][j]*Inv%Mod;
for(int j=i+1;j<=n;j++)
for(int k=n+1;k>=i;k--)
A[j][k]=(A[j][k]-A[i][k]*A[j][i]%Mod+Mod)%Mod;
}
ans[n]=A[n][n+1];
for(int i=n-1;i;i--){
ans[i]=A[i][n+1];
for(int j=n;j>i;j--)
ans[i]=(ans[i]-ans[j]*A[i][j]%Mod+Mod)%Mod;
}
for(int i=1;i<=n;i++) printf("%lld ",ans[i]);
}