DP|[codeforces 917D]Stranger Trees

题目大意
给定一棵n个节点的树。对于k=0到n-1,输出有多少棵n个节点的带标号无根树恰好与给定的树有k条公共边。答案模 109+7
n≤100
分析
【DP|[codeforces 917D]Stranger Trees】我们从prufer序的角度考虑这道题。
选择了若干条原树的边后,我们得到一些联通块。把这些联通块都缩在一起,假设剩下m个点,接下来我们要去连接这些联通块,得到一棵树,然后考虑这棵树的prufer序是怎样的。
由于当前叶子的父亲所表示的联通块G中任意一个点都可能与叶子联通块相连,所以有 |G| 种可能。那么prufer序的m-2位共有 (∑mi=1|Gi|)m?2=nm?2 种可能情况。
但是prufer序不能完全表示这棵树,因为由prufer序只能确定父亲联通块中哪个点连了出去,不知道连到哪个点,假设叶子联通块是G’,那么还有 |G′| 种可能情况。再算上连接最后两个联通块的边的可能情况,所以上式还要乘上 ∏mi=1|Gi| 才能正确算出剩下m个联通块的答案。
知道上面的东西就可以dp了。设f[i][j][k]表示原树中以i为根的子树,分成了j个联通块(可以由此知道选了多少条原树的边),其中i所在联通块大小为k时的答案。转移时枚举儿子,然后枚举儿子的后两维。转移看起来有五重循环,但是可以通过树形背包的分析方法发现时间复杂度其实是 O(n4) 的
这样就做完了吗?没有!
我们发现这样算出来的是与原树至少有k条公共边的答案,需要一个 O(n2) 的容斥算出正确答案。这个就不具体写了。

#include #include #include using namespace std; const int N=105,mo=1e9+7; typedef long long LL; int n,f[N][N][N],Size[N],g[N][N],ans,C[N][N],F[N],tot,h[N],e[N<<1],nxt[N<<1]; void Add(int x,int y) { e[++tot]=y; nxt[tot]=h[x]; h[x]=tot; }void dp(int x,int y) { Size[x]=1; f[x][1][1]=1; for (int i=h[x]; i; i=nxt[i]) if (e[i]!=y) { dp(e[i],x); memset(g,0,sizeof(g)); for (int j=1; j<=Size[x]; j++) { for (int p=1; p<=Size[x]-j+1; p++) if (f[x][j][p]) { for (int k=1; k<=Size[e[i]]; k++) { for (int q=1; q<=Size[e[i]]-k+1; q++) if (f[e[i]][k][q]) { g[j+k][p]=(g[j+k][p]+(LL)f[x][j][p]*f[e[i]][k][q]%mo*q)%mo; g[j+k-1][p+q]=(g[j+k-1][p+q]+(LL)f[x][j][p]*f[e[i]][k][q])%mo; } } } } Size[x]+=Size[e[i]]; memcpy(f[x],g,sizeof(g)); } }int main() { scanf("%d",&n); for (int i=1,x,y; i

    推荐阅读