构树 题解

构树 题解

好题,除了毒瘤卡空间。

“恰好” 这个形式很二项式反演。设 f(n) 表示树上钦定 n 条边和原树相同的方案数,g(n) 表示树上恰好有 n 条边和原树相同的方案数,那么原先的形式是:

f(n)=in(in)g(i)

二项式反演得到:

g(n)=in(1)in(in)f(i)

于是考虑如何求出 f(i)

考虑 f(k) 的求解。由扩展 Cayley 公式,如果连上 k 条边后形成了 m 个连通块,第 i 个连通块的大小是 ai,那么连边后形成的生成树方案数是 nm2ai。考虑 ai 的组合意义,是在每一个连通块中选出一个点的方案数。

那么容易得到树形 dp:我们设 dpi,j,0/1 表示 i 子树中选择 j 条边,i 所在的连通块中选/未选一个点的方案数。

转移的式子和树形背包是类似的,对于 (x,y),枚举选择 (x,y) 和不选 (x,y) 的情况,注意对于不选 (x,y) 的情况要额外 ×n 表示 Cayley 公式中合并连通块所需要的 n。注意到这样会多算一个,因此得出 f(1) 后需要 ×1n

那么二项式反演即可,时间复杂度是相当于将整棵树每个节点的 siz 遍历一遍,就是 O(n2) 的。

考虑这样的空间复杂度也是 O(n2),开不下。注意到对于 (x,y),在 y 作完对 x 的贡献之后就没用了,于是考虑使用 std::vector 动态管理内存,每次 yx 做出贡献之后使用 vector<int>().swap(v) 释放内存即可。

注意当计数题思路进行不下去的时候考虑某一项的组合意义转化为可以形象求解的问题。

代码:

#include <bits/stdc++.h>
#define N 8005
#define int long long
#define mod 1000000007
using namespace std;
int n;
struct Node {
	int to, nxt;
} e[N << 1];
int head[N], cnt;
void add(int u, int v) {
	e[++cnt].to = v;
	e[cnt].nxt = head[u];
	head[u] = cnt;
} 
void ad(int &x, int y) {
	x += y;
	if (x > mod)
		x -= mod;
}
int siz[N];
int tmp[N][2];
vector<int>v0[N], v1[N];
void dfs(int x, int fa) {
	siz[x] = 1;
	v0[x].resize(n), v1[x].resize(n);
	v0[x][0] = v1[x][0] = 1;
	for (int i = head[x]; i; i = e[i].nxt) {
		int y = e[i].to;
		if (y == fa)
			continue;
		dfs(y, x);
		for (int j = 0; j < siz[x]; j++)
			for (int k = 0; k < siz[y]; k++) {
				ad(tmp[j + k + 1][0], v0[x][j] * v0[y][k] % mod);
				ad(tmp[j + k + 1][1], (v0[x][j] * v1[y][k] % mod + v1[x][j] * v0[y][k] % mod) % mod);
				ad(tmp[j + k][0], v0[x][j] * v1[y][k] % mod * n % mod);
				ad(tmp[j + k][1], v1[x][j] * v1[y][k] % mod * n % mod);
			}
		siz[x] += siz[y];
		for (int j = 0; j < siz[x]; j++)
			v0[x][j] = tmp[j][0], v1[x][j] = tmp[j][1], tmp[j][0] = tmp[j][1] = 0;
		vector<int>().swap(v0[y]);
		vector<int>().swap(v1[y]);
	}
}
int qpow(int x, int y) {
	int ans = 1;
	while (y) {
		if (y & 1)
			ans = ans * x % mod;
		x = x * x % mod;
		y >>= 1;
	}
	return ans;
}
int fac[N], inv[N];
int C(int n, int m) {
	if (n < m)
		return 0;
	return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
signed main() {
	freopen("tree.in", "r", stdin);
	freopen("tree.out", "w", stdout);
	cin >> n;
	fac[0] = inv[0] = 1;
	for (int i = 1; i < N; i++)
		fac[i] = fac[i - 1] * i % mod, inv[i] = qpow(fac[i], mod - 2);
	for (int i = 1; i < n; i++) {
		int x, y;
		scanf("%lld%lld", &x, &y);
		add(x, y);
		add(y, x);
	}
	dfs(1, 0);
	const int inv = qpow(n, mod - 2);
	for (int i = 0; i < n; i++) 
		v1[1][i] = v1[1][i] * inv % mod;
	for (int i = 0; i < n; i++) {
		int fg = 1, ans = 0;
		for (int j = i; j < n; j++)
			ans = (ans + fg * C(j, i) % mod * v1[1][j] % mod + mod) % mod, fg *= -1;
		cout << ans << " ";
	}
	puts("");
	return 0;
}
posted @   长安19路  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示