题解:CF2117F Wildflower

一些记号与约定

下文中用 \(sub_u\) 表示以 \(u\) 点为根的子树,\(dep_u\) 表示 \(u\)\(1\) 的路径上的节点个数,\(sz_u\) 表示 \(sub_u\) 的大小。

题意

给定一个 \(n\) 个点的、以 \(1\) 为根的树。求有多少个长度为 \(n\) 的序列 \(a\),满足:

  • 对于 \(1\leq i\leq n\)\(a_i\in\{1,2\}\)
  • 定义 \(s_u=\sum_{v\in sub_u}a_v\),则 \(s\) 两两不同。

答案对 \(10^9+7\) 取模。多测,\(1\leq T\leq 10^4\)\(2\leq n\leq\sum{n}\leq 2\times 10^5\)

题解

首先一个显然的观察是,若这个树有 \(>2\) 个叶子节点,则答案必然为 \(0\)。因为 \(s\) 必然包含所有叶子节点的 \(a\) 值,如果有 \(>2\) 个叶子节点,这些叶子节点的 \(a\) 值必然会有重复。

于是我们只需考虑叶子节点个数 \(\leq 2\) 的情况。

先来考虑一条链,也就是叶子节点个数 \(=1\)。因为 \(1\leq a_i\leq 2\),所以此时任意的 \(a\) 都能满足 \(s\) 两两不同的要求,方案数为 \(2^n\)

再来考虑有恰好 \(2\) 个叶子节点的情况,设它们分别为 \(u,v\),令 \(d=\operatorname{lca}(u,v)\)。不难发现,我们可以拆成两部分:一部分是 \(d\)\(1\) 的路径,另一部分是 \(sub_d\) 除去 \(d\) 的部分。显然这两部分之间的计数互不影响,因为只要第二部分是合法的,我们接上第一部分的链也一定是合法的。

第一部分的方案数显然是 \(2^{dep_d}\)

对于第二部分,不妨从叶子节点开始考虑,显然必须有 \(\{a_u,a_v\}=\{1,2\}\),我们钦定 \(a_u=1,a_v=2\)。从下往上推:

  • 首先 \(a_{fa_u}\) 必须填 \(2\),否则会有 \(s_{fa_u}=s_v=2\),于是 \(s_{fa_u}\) 也必须填 \(2\)
  • 那么 \(a_{fa_{fa_u}}\) 也,必须填 \(2\),否则会有 \(s_{fa_{fa_u}}=s_{fa_v}=4\),于是 \(s_{fa_{fa_v}}\) 也必须填 \(2\)
  • ……

\(d\)\(u\) 方向上的儿子为 \(p\),在 \(v\) 方向上的儿子为 \(q\)

于是如果 \(sz_p=sz_q\),那么当叶子确定了之后,只能一路向上填 \(2\),方案数就是填叶子的方案数,即为 \(2\)

如果 \(sz_p\neq sz_q\),钦定 \(sz_p<sz_q\)。若令 \(a_u=1,a_v=2\),则一路向上填了 \(sz_p-1\)\(2\) 后,\(v\) 的部分就没有限制了,方案数为 \(2^{sz_q-sz_p}\)。若令 \(a_u=2,a_v=1\),一路向上填了 \(sz_p-1\)\(2\) 后,此时 \(v\) 部分向上的一个位置不能填 \(1\),然后就没有限制了,方案数为 \(2^{sz_q-sz_p-1}\)

所以第二部分的总方案数为 \(2^{sz_q-sz_p-1}+2^{sz_q-sz_p}\)。那么答案就是 \(2^{dep_d}(2^{sz_q-sz_p-1}+2^{sz_q-sz_p})\)

时间复杂度 \(\mathcal{O}(\sum{n})\)

代码

实现时,找到度数最大的点就是 \(d\) 了。

#include <iostream>

using namespace std;

#define lowbit(x) ((x) & -(x))
#define chk_min(x, v) (x) = min((x), (v))
#define chk_max(x, v) (x) = max((x), (v))
typedef long long ll;
typedef pair<int, int> pii;
const int N = 2e5 + 5, MOD = 1e9 + 7;

inline int add(int x, int y) { return x += y, x >= MOD ? x - MOD : x; }
inline int sub(int x, int y) { return x -= y, x < 0 ? x + MOD : x; }
inline void cadd(int &x, int y) { x += y, x < MOD || (x -= MOD); }
inline void csub(int &x, int y) { x -= y, x < 0 && (x += MOD); }

int T, n, mx, ans, deg[N], sz[N], dep[N];

struct AdjList {
	int tot, head[N], nxt[N << 1], to[N << 1];
	inline void init() {
		tot = 0;
		for (int i = 1; i <= n; ++i) head[i] = -1;
	}
	inline void ins(int x, int y) { ++deg[y], to[++tot] = y, nxt[tot] = head[x], head[x] = tot; }
} t;

inline int qpow(int a, int b) {
	int res = 1;
	for (; b; b >>= 1) {
		if (b & 1) res = (ll)res * a % MOD;
		a = (ll)a * a % MOD;
	}
	return res;
}
inline void dfs(int x, int fx) {
	sz[x] = 1;
	for (int i = t.head[x]; i != -1; i = t.nxt[i]) {
		int y = t.to[i];
		if (y == fx) continue;
		dep[y] = dep[x] + 1;
		dfs(y, x), sz[x] += sz[y];
	}
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0);
    cin >> T;
    while (T--) {
    	cin >> n, t.init();
    	for (int i = 1; i <= n; ++i) deg[i] = 0;
    	for (int i = 1, u, v; i < n; ++i) cin >> u >> v, t.ins(u, v), t.ins(v, u);
    	int cnt = 0;
    	for (int i = 2; i <= n; ++i) if (deg[i] == 1) ++cnt;
    	if (cnt > 2) { cout << "0\n"; continue; }
    	if (cnt == 1) cout << qpow(2, n) << '\n';
    	else {
    		dep[1] = 1, dfs(1, 0);
    		int p = 1;
    		for (int i = 2; i <= n; ++i) if (deg[i] == 3) { p = i; break; }
    		ans = qpow(2, dep[p]);
    		int a = n + 1, b = 0;
    		for (int i = t.head[p]; i != -1; i = t.nxt[i]) {
    			int y = t.to[i];
    			if (dep[y] < dep[p]) continue;
    			chk_min(a, sz[y]), chk_max(b, sz[y]);
    		}
    		if (a == b) ans = (ll)ans * 2 % MOD;
    		else ans = (ll)ans * add(qpow(2, b - a - 1), qpow(2, b - a)) % MOD;
    		cout << ans << '\n';
    	}
    }
    return 0;
}
posted @ 2025-06-21 16:20  P2441M  阅读(36)  评论(0)    收藏  举报