CF方向
Luogu方向
一个显然的转化是:恰好 k k k 条边不好求,所以把 恰好
转化成 至少
,然后进行二项式反演
令 f i f_i fi 为恰好 k k k 条边 . . . ... ..., g i g_i gi 为至少 k k k 条边 . . . ... ...
那么 f i = ∑ j = i n − 1 g j ( − 1 ) j − i ( j i ) f_i=\sum\limits_{j=i}^{n-1}g_j(-1)^{j-i}\binom{j}{i} fi=j=i∑n−1gj(−1)j−i(ij)
这一部分的时间复杂度是 O ( n 2 ) O(n^2) O(n2) 的
考虑求解 g g g,可以发现保留的边数和连通块个数是可以对应的,所以接下来只对连通块考虑
这里有一个结论是:如果 m m m 个连通块的大小分别是 s i z 1 , s i z 2 , . . . , s i z m siz_1,siz_2,...,siz_m siz1,siz2,...,sizm,且每个点都有编号,则把它们构成生成树的方案数为 n m − 2 ∏ s i z i n^{m-2}\prod siz_i nm−2∏sizi
证明可以见 oiwiki-prufer 中的 图连通方案数
我们发现直接把上面的式子进行 d p dp dp 的时间复杂度是 O ( n 3 ) O(n^3) O(n3) 的,即令 d p i , j , k dp_{i,j,k} dpi,j,k 表示在 i i i 的子树中选出了 j j j 个连通块, i i i 所在连通块有 k k k 个点的方案数
我们考虑 ∏ s i z i \prod siz_i ∏sizi 的组合意义,即在每个连通块中选出一个点的方案数
这样就可以令 d p i , j , 0 / 1 dp_{i,j,0/1} dpi,j,0/1 表示在 i i i 的子树中,选出了 j j j 个连通块, i i i 所在连通块是否选过点的方案数
转移比较好转移,根据背包 d p dp dp 的时间复杂度分析,可以做到 O ( n 2 ) O(n^2) O(n2)
#include
using namespace std;
const int N=110,P=1e9+7;
typedef long long LL;
int n,g[N],f[N],C[N][N];
int dp[N][N][2],t[N][2],siz[N];
int e[N<<1],ne[N<<1],h[N],idx;
inline int read(){
int FF=0,RR=1;
char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
return FF*RR;
}
inline void inc(int &x,LL y){
x=(x+y)%P;
if(x<0) x+=P;
}
void dfs(int u,int fa){
dp[u][1][0]=dp[u][1][1]=1,siz[u]=1;
for(int i=h[u];~i;i=ne[i]){
int v=e[i];
if(v==fa) continue;
dfs(v,u);
for(int j=1;j<=siz[u]+siz[v];j++) t[j][0]=t[j][1]=0;
for(int j=1;j<=siz[u];j++) for(int k=1;k<=siz[v];k++){
inc(t[j+k][0],1ll*dp[u][j][0]*dp[v][k][1]);
inc(t[j+k][1],1ll*dp[u][j][1]*dp[v][k][1]);
inc(t[j+k-1][0],1ll*dp[u][j][0]*dp[v][k][0]);
inc(t[j+k-1][1],(1ll*dp[u][j][0]*dp[v][k][1]+1ll*dp[u][j][1]*dp[v][k][0]));
}
siz[u]+=siz[v];
for(int j=1;j<=siz[u];j++) dp[u][j][0]=t[j][0],dp[u][j][1]=t[j][1];
}
}
void add(int x,int y){ e[idx]=y,ne[idx]=h[x],h[x]=idx++;}
int qmi(int a,int b){
if(b<0) return qmi(a,P-2);
int res=1;
for(;b;b>>=1){
if(b&1) res=1ll*res*a%P;
a=1ll*a*a%P;
}
return res;
}
int main(){
n=read();
memset(h,-1,sizeof(h));
for(int i=1;i<n;i++){
int x=read(),y=read();
add(x,y),add(y,x);
}
dfs(1,-1);
for(int i=1;i<=n;i++) g[n-i]=1ll*dp[1][i][1]*qmi(n,i-2)%P;
C[0][0]=1;
for(int i=1;i<=n;i++) for(int j=0;j<=i;j++) C[i][j]=(!j||i==j)?1:(C[i-1][j-1]+C[i-1][j])%P;
for(int i=0;i<n;i++)
for(int j=i,neg=1;j<n;j++,neg*=-1)
inc(f[i],1ll*g[j]*neg*C[j][i]);
for(int i=0;i<n;i++) printf("%d ",f[i]);puts("");
fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
return 0;
}
/*
f[i][j][0/1]: 在i的子树中,分成了j个连通块,i连通块内是否选过的方案数
*/