Loading

:D 获取中...

从一道换根 dp 题说说 up and down

拿到 \(S(i) = \sum\limits_{j = 1} ^ n \operatorname{dist}(i, j) ^ k\)

首先我们啥也做不了,只能根据 Stirling 数的那堆柿子硬拆开,有 \(m ^ n = \sum\limits_{i = 0} ^ m {n \brace i} i! \binom{m}{i} = \sum\limits_{i = 0} ^ n {n \brace i} i! \binom{m}{i}\)。代入:

\[S(i) = \sum\limits_{j = 1} ^ n \sum\limits_{l = 0} ^ k {k \brace l} l! \binom{\operatorname{dist}(i, j)}{l} \]

交换求和顺序:

\[S(i) = \sum\limits_{l = 0} ^ k {k \brace l} l! \sum\limits_{j = 1} ^ n \binom{\operatorname{dist}(i, j)}{l} \]

那么重点就是计算 \(\sum\limits_{j = 1} ^ n \binom{\operatorname{dist}(i, j)}{l}\)。而我们知道 \(\binom{n}{m} = \binom{n - 1}{m} + \binom{n - 1}{m - 1}\)。考虑直接设 \(f_{u, i}\) 表示 \(u\) 的子树中的所有 \(v\)\(\sum\limits_{j = 1} ^ n \binom{\operatorname{dist}(u, v)}{i}\)。则有 \(f_{u, i} = f_{v, i} + f_{v, i - 1}\),其中 \(v\)\(u\) 的直接儿子。

然后 \(n \leq 5 \times 10 ^ 4\),需要换根。这里是一种 up and down 的换根方法,好写好想。具体思路就是:

  1. 计算好固定某一点 \(u\) 为根的 dp 值(相当于得到了以该点为根的答案值)
  2. 枚举其子节点 \(v\),在以 \(u\) 为根的 dp 值基础上减去以 \(v\) 为根的子树的贡献,用剩下的结果(\(v\) 为根时 \(u\) 子树的答案)去更新 \(v\) 的 dp 值。这样 \(v\) 的答案计算完毕。回到 1. 即可。

要注意了!自己换根能力菜的一批👎

namespace liuzimingc {
const int N = 5e4 + 5, M = 155, MOD = 1e4 + 7;
#define int long long

int n, k, f[N][M], dp[N][M], t[N], S[M][M], fac[N];
vector<int> e[N];

void dfs(int u, int fa) {
	f[u][0] = 1;
	for (const int &v : e[u]) {
		if (v == fa) continue;
		dfs(v, u);
		for (int i = 0; i <= k; i++)
			(f[u][i] += f[v][i] + (i ? f[v][i - 1] : 0)) %= MOD;
	}
}

void dfs2(int u, int fa) {
	for (const int &v : e[u]) {
		if (v == fa) continue;
		for (int i = 0; i <= k; i++) dp[v][i] = f[v][i]; // 继承
		for (int i = 0; i <= k; i++) t[i] = (dp[u][i] - f[v][i] - (i ? f[v][i - 1] : 0) + 2 * MOD) % MOD; // 减去贡献
		for (int i = 0; i <= k; i++) (dp[v][i] += t[i] + (i ? t[i - 1] : 0)) %= MOD; // 重新计算
		dfs2(v, u);
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr); cout.tie(nullptr);
	cin >> n >> k;
	for (int i = 1; i < n; i++) {
		int u, v;
		cin >> u >> v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs(1, 0);
	for (int i = 0; i <= k; i++) dp[1][i] = f[1][i]; // 这个直接赋值
	dfs2(1, 0);
	for (int i = 0; i <= k; i++) {
		S[i][0] = !i;
		for (int j = 1; j <= k; j++)
			S[i][j] = (S[i - 1][j - 1] + j * S[i - 1][j]) % MOD;
	}
	fac[0] = 1;
	for (int i = 1; i <= k; i++) fac[i] = fac[i - 1] * i % MOD;
	for (int i = 1; i <= n; i++) {
		int ans = 0;
		for (int j = 0; j <= k; j++)
			(ans += S[k][j] * fac[j] % MOD * dp[i][j] % MOD) %= MOD;
		cout << ans << endl; 
	}
	return 0;
}
#undef int
} // namespace liuzimingc

下面是最近的比较能体现思路的代码:

void dfs(int u, int fa) {
	f[u] = 1;
	siz[u] = 1;
	for (int v : e[u]) {
		if (v == fa) continue;
		dfs(v, u);
		(f[u] *= f[v] * inv[siz[v]] % MOD) %= MOD;
		siz[u] += siz[v];
	}
	(f[u] *= fac[siz[u] - 1]) %= MOD;
}

void dfs2(int u, int fa) {
	(ans += f[u] * f[u] % MOD) %= MOD;
	for (int v : e[u]) {
		if (v == fa) continue;
		int _f = f[u], _siz = siz[u];
		(f[u] *= qkpow(f[v] * inv[siz[v]] % MOD, MOD - 2)) %= MOD;
		(f[u] *= inv[siz[u] - 1]) %= MOD;
		siz[u] -= siz[v];
		(f[u] *= fac[siz[u] - 1]) %= MOD; // 减去 v 贡献
		(f[v] *= inv[siz[v] - 1]) %= MOD;
		siz[v] += siz[u];
		(f[v] *= fac[siz[v] - 1]) %= MOD;
		(f[v] *= f[u] * inv[siz[u]] % MOD) %= MOD; // 加上 u 贡献
		dfs2(v, u);
		f[u] = _f, siz[u] = _siz; // 回退!!!
	}
}
posted @ 2024-09-12 22:00  liuzimingc  阅读(10)  评论(1编辑  收藏  举报