【CF917D】Stranger Trees

题意:给你一棵n个点的树,对于k=1...n,问你有多少有标号的n个点的树,与给出的树有恰好k条边相同?

$n\le 100$

题解:我们先考虑容斥,求出和给出的树至少有k个点相同的树的数量。我们先选出原树中的k条边,然后剩下的边随便连。选出k条边后,原树被分成n-k个连通块,设其大小分别为$siz_1,siz_2...siz_{n-k}$。那么剩下的边随便连的方案数是多少呢?我们不妨把每个连通块看成一个点,答案变成n个点的完全图的生成树个数,根据Prufer序列知道这个答案是$n^{n-2}$。但是这里一个连通块的大小并不是1。对于一个大小为$siz$的连通块,如果它在Prufer序列中出现了j次,那么它对答案的贡献其实是$siz^{j+1}$(因为它的度数是j+1)。我们可以先把$\prod\limits_{i=1}^{n-k}siz_i$提出来,然后对于Prufer序列中的每个位置,如果它是第i个连通块,则贡献为$siz_i$,所以总的贡献为$\sum\limits_{i=1}^{n-k}siz_i=n$,那么答案就是$\prod\limits_{i=1}^{n-k}siz_i\times n^{n-k-2}$。

所以我们考虑树形DP,用f[x][a][b]表示在x的子树中,已经连了a条边,包含x的连通块大小为b的总贡献。最后容斥一发即可。

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
typedef long long ll;
const ll P=1000000007;
int n,m,cnt;
int to[210],nxt[210],head[110],siz[110];
ll f[110][110][110],g[110][110],c[110][110],h[110],bt[110];
inline int rd()
{
int ret=0,f=1; char gc=getchar();
while(gc<'0'||gc>'9') {if(gc=='-') f=-f; gc=getchar();}
while(gc>='0'&&gc<='9') ret=ret*10+gc-'0',gc=getchar();
return ret*f;
}
void dfs(int x,int fa)
{
f[x][1][0]=1,siz[x]=1;
for(int i=head[x],j,k,a,b,y;i!=-1;i=nxt[i]) if(to[i]!=fa)
{
y=to[i],dfs(to[i],x);
memset(g,0,sizeof(g));
for(j=1;j<=siz[x];j++) for(k=1;k<=siz[y];k++)
{
for(a=0;a<siz[x];a++) for(b=0;b<siz[y];b++)
{
g[j+k][a+b+1]=(g[j+k][a+b+1]+f[x][j][a]*f[y][k][b])%P;
g[j][a+b]=(g[j][a+b]+f[x][j][a]*f[y][k][b]%P*k)%P;
}
}
memcpy(f[x],g,sizeof(g));
siz[x]+=siz[y];
}
}
inline void add(int a,int b)
{
to[cnt]=b,nxt[cnt]=head[a],head[a]=cnt++;
}
int main()
{
n=rd();
int i,j,a,b;
memset(head,-1,sizeof(head));
for(i=1;i<n;i++) a=rd(),b=rd(),add(a,b),add(b,a);
dfs(1,0);
for(bt[0]=i=1;i<=n;i++) bt[i]=bt[i-1]*n%P;
for(i=0;i<=n;i++) for(c[i][0]=j=1;j<=i;j++) c[i][j]=(c[i-1][j-1]+c[i-1][j])%P;
for(i=1;i<n;i++) for(j=0;j<n;j++) h[j]=(h[j]+f[1][i][j]*i)%P;
h[n-1]=1;
for(i=0;i<n-1;i++) h[i]=h[i]*bt[n-i-2]%P;
for(i=n-1;i>=0;i--)
{
for(j=i+1;j<n;j++) h[i]=(h[i]-c[j][i]*h[j])%P;
h[i]=(h[i]+P)%P;
}
for(i=0;i<n;i++) printf("%lld ",h[i]);
return 0;
}
05-02 07:13