D. Rebuild Tree
前置芝士:prufer序列
prufer序列是将n个点的标号的无根树对应到长度为n-2,每个位置值域为[1,n]的序列。
prufer序列的构造方法是每次将树中编号最小的树叶删除,将树叶连接的点加入prufer序列。
树上点x的度数=prufer序列中x出现的次数+1。
Solution
题意相当于砍掉k条边再加上k条边,构成一棵树的方案数。
首先来考虑当砍掉的k条边确定时,此时树被分成了k+1棵树,我们设每棵树的节点个数是
我们把这些树抽象成点,因为最终就是要在这k+1棵树之间连k条边使得最终成一棵树;考虑当连边的方案已经确定时
此时这k+1个点构成的树的prufer序列也已经确定,还原到真实的方案树还要乘上每条边连到了哪个点的方案数,显然对于同一棵树内,连哪个点都是可以的,所以我们需要乘上,其中表示i的度数,由prufer序列的知识我们可以得知,这个式子可以写成
发现与连边的方案无关,那么这部分就可以提出来,此时这k+1棵树的总贡献就可以写成
考察右边的式子,是i树在prufer序列中出现的次数,我们知道,此时的prufer序列长度是,每个位置的值域在[1,k+1],而当一个位置选的时候,算回总贡献是要乘上的,也就是说一种选择方案的贡献不是1,而是,那么种prufer序列的总贡献其实就是
那么我们的答案就简化为
所以只需要算出
考虑将问题转化为等价问题:删掉条边且在每个联通块选一个点的方案数。
设表示子树内,删掉了条边,所在的联通块是否选了点的方案数
然后就是经典树上背包了
ll dp[N][101][2], tmp[101][2]; int n, k, sz[N]; vector<int>to[N]; ll ksm(ll a, ll b) { ll ans = 1; for (; b; b >>= 1, (a *= a) %= mod) if (b & 1)(ans *= a) %= mod; return ans; } void DP(int x, int fa) { sz[x] = 1; dp[x][0][0] = 1; dp[x][0][1] = 1; for (auto y : to[x]) { if (y == fa)continue; DP(y, x); memset(tmp, 0, sizeof(tmp)); for (int i = 0; i < sz[x] && i <= k; ++i) { for (int j = 0; j < sz[y] && i + j <= k; ++j) { //不删当前边 (tmp[i + j][0] += dp[x][i][0] * dp[y][j][0] % mod) %= mod; (tmp[i + j][1] += dp[x][i][1] * dp[y][j][0] % mod + dp[x][i][0] * dp[y][j][1] % mod) %= mod; if (i + j == k)continue; //删除当前边 (tmp[i + j + 1][0] += dp[x][i][0] * dp[y][j][1] % mod) %= mod; (tmp[i + j + 1][1] += dp[x][i][1] * dp[y][j][1] % mod) %= mod; } } memcpy(dp[x], tmp, sizeof(tmp)); sz[x] += sz[y]; } } int main() { n = fast_IO::read(), k = fast_IO::read(); for (int i = 1; i < n; ++i) { int x = fast_IO::read(), y = fast_IO::read(); to[x].push_back(y); to[y].push_back(x); } DP(1, 0); printf("%lld", dp[1][k][1] * ksm(n, k - 1) % mod); return 0; }