传送门 题目大意 一棵$n$个点的树,一个点集$S$的权值定义为把这个点击连成一个联通块的最少边数,求: $$ans=\sum_{S\in U}f(S)^k$$ 题解 这题跟gdoi那道题差不多 先把柿子化一下变成 $$an
传送门
题目大意
一棵$n$个点的树,一个点集$S$的权值定义为把这个点击连成一个联通块的最少边数,求:
$$ans=\sum_{S\in U}f(S)^k$$
题解
这题跟gdoi那道题差不多
先把柿子化一下变成
$$ans=\sum_{i=0}^k \begin{Bmatrix}k\\i\end{Bmatrix} i! \sum_{S\in U}\begin{pmatrix}f(S)\\i\end{pmatrix}$$
然后我们就相当于去统计大小为$i$的边集的贡献
这个可以通过dp来实现
定义$f_{x,i}$表示$x$子树内所有点与父亲的连边中选出了$i$条边,子树内选择的点的方案数
dp过程就是首先算出不包括$x$和父亲的边的方案数,然后再加上这条边就可以了
在$x$和父亲的边加入边集时,要注意$x$的子树内外会不会一个点都没选
减去这些方案就可以了
Code
#include <cstdio> #include <cstring> #include <cstdlib> #include <algorithm> #define LL long long using namespace std; const LL Maxn = 100010; const LL Maxk = 210; const LL Mod = 1e9+7; LL f[Maxn][Maxk], g[Maxk]; LL h[Maxk]; LL n, K; struct node { LL y, next; }a[Maxn<<1]; LL first[Maxn], len; void ins(LL x, LL y) { len++; a[len].y = y; a[len].next = first[x]; first[x] = len; } LL Str2[Maxk][Maxk], jc[Maxk]; LL siz[Maxn]; void up(LL &x, LL y) { x = (x + y) % Mod; } void dfs(LL x, LL fa) { f[x][0] = 2; siz[x] = 1; for(LL k = first[x]; k; k = a[k].next){ LL y = a[k].y; if(y == fa) continue; dfs(y, x); for(LL i = 0; i < siz[x]+siz[y] && i <= K; i++) g[i] = 0; for(LL i = 0; i < siz[x] && i <= K; i++){ for(LL j = 0; j <= siz[y] && j <= K-i; j++) up(g[i+j], f[x][i]*f[y][j]); } siz[x] += siz[y]; for(LL i = 0; i < siz[x] && i <= K; i++) f[x][i] = g[i]; } if(x != 1){ for(LL i = 0; i < K; i++){ up(h[i+1], Mod-f[x][i]); if(i == 0) up(h[1], 1); } } else for(LL i = 1; i <= K; i++) up(h[i], f[x][i]); for(LL i = K; i > 0; i--) up(f[x][i], f[x][i-1]); up(f[x][1], Mod-1); } int main() { LL i, j, k; scanf("%lld%lld", &n, &K); Str2[0][0] = 1; for(i = 1; i <= K; i++){ for(j = 1; j <= i; j++) Str2[i][j] = (j*Str2[i-1][j]+Str2[i-1][j-1])%Mod; } jc[0] = 1; for(i = 1; i <= K; i++) jc[i] = jc[i-1]*i%Mod; for(i = 1; i < n; i++){ LL x, y; scanf("%lld%lld", &x, &y); ins(x, y); ins(y, x); } dfs(1, 0); LL ans = 0; for(i = 1; i <= K; i++) up(ans, Str2[K][i]*jc[i]%Mod*h[i]); printf("%lld\n", ans); return 0; }