更为简单的做法

· · 题解

前一半是我自己做的,所以是一个 Sub 一个 Sub 写的题解。

Sub1

考虑树上的某个点集的导出子图。是一个森林。我们认为孤立点也是叶子。 让我们重新描述这个 $\preccurlyeq$ 关系。设 $f_{T, S}$ 表示对于一棵树 $T$,$T$ 对点集 $S$ 的导出子图的叶子数量,$g_{T, S}$ 表示对于一棵树 $T$,$T$ 对点集 $S$ 的超集的导出子图的叶子数量最小值。 那么设 $T_1=(V_1,E_1)$,$T_2=(V_2,E_2)$,$T_1 \preccurlyeq T_2$ 的充要条件是: $$\forall S \subseteq V_1,g_{T_1,S} \leq f_{T_2, S}$$ 其实这也与 $$\forall S \subseteq V_1,g_{T_1,S} \leq g_{T_2, S}$$ 等价。(卡点) 因此树的等价关系就是 $$\forall S \subseteq V_1, g_{T_1, S}=g_{T_2, S}$$ 我们可以使用 SOS DP 计算出 $g$ 并记录哈希值,不同哈希值个数即为等价类个数。 ### Sub2 不难发现 $g_{T, S}$ 是不需要 SOS DP 的。贪心地,$g_{T, S}$ 的取值即为 $S$ 的斯坦纳子树的叶子个数。 那我们分析,如果 $T_2$ 是一个以点 $u$ 为中心的菊花图,树 $T_1 \preccurlyeq T_2$ 需要满足什么条件。 我们声称:对于 $T_2$ 只取 $u$ 及两个叶子与 $T_1$ 的对应点集比对,和比对所有点集互为充要条件。 为什么呢?贪心地,如果取了 $u$ 和两个叶子的情况下,少取一个叶子就少选了一个点,叶子数不变,一定不优;多取一个叶子多选了一个点,叶子数 $+1$,而多选一个点叶子数至多 $+1$,也一定不优。于是结论正确。 根据这个,容易观察出图结构:点 $u$ 在 $T_1$ 上的所有子树均为链。分类讨论: + 整棵树为链,构成一个等价类。 + 整棵树不为链,当且仅当交换两个 $u$ 同一子树内的节点时和原树属于同一等价类。 整棵树不为链的情况即为 $n-1$ 个不同元素划入 $k$ 个相同集合的方案数。因此总答案为: $$1+\sum_{i=3}^{n-1}{n-1\brace i}$$ $n$ 很小,直接 $O(n^2)$ 递推第二类斯特林数即可。 ### Sub7 非常唐的 Sub。显然任选一个点的 $f$ 值为 $1$,任选 $>1$ 个点的 $f$ 值至少为 $2$,而链任选 $>1$ 个点的 $g$ 值恰好为 $2$,意味着任意树都合法。这样就变成了有标号树计数板子,根据 Prufer 序列输出 $n^{n-2}$ 即可。 ### Sub8~11 前面的刻画太不牛了,来一个稍微牛一点的刻画! 贪心地,$T_1 \preccurlyeq T_2$ 的充要条件是 $T_2$ 任意两个叶子之间的路径在 $T_1$ 中都位于一条路径上。 那对于 Sub8,有什么用呢? 给出另一个充要条件:对于 $T_1$ 上所有度数 $\ge 3$ 的点 $u$,$T_1$ 上在 $u$ 不同子树中的节点,$T_2$ 上也在 $u$ 的不同子树中。(卡点) 为什么呢?设有三个点 $v_1,v_2,v_3$ 在 $u$ 的不同子树中,$u$ 应该在 $v_1 \leftrightarrow v_2$ 的路径上、$v_1 \leftrightarrow v_3$ 的路径上、$v_2 \leftrightarrow v_3$ 的路径上,这样就可以得到了。 到这里 Sub8 的做法就呼之欲出了:我们只需要通过度数 $\ge 3$ 的点把图切成若干条链,利用 Cayley 公式(即 $n^{n-2}$)算出每个部分的方案数再相乘即为答案。(卡点) 那 9~10 呢?直接对每棵树跑一遍,对点集交集 Cayley 公式就好了。 Sub11 可以 bitset 优化。具体地,采用辅助图,把极长链点集连成完全图,我们求的答案恰好是辅助图取交后的生成树个数。 _______ 接下来的部分就不是我自己能做出来的了。 ### Sub3~6 定义若干个点共线为在同一条路径上。 考虑前面的刻画:$T_1 \preccurlyeq T_2$ 的充要条件是 $T_2$ 任意两个叶子之间的路径在 $T_1$ 中都位于一条路径上。 弱化它:$T_1 \preccurlyeq T_2$ 的充要条件是 $T_2$ 中任意共线的三个点在 $T_1$ 中共线。 接下来,将树拆成极长链,满足: + 内部点度数均为 $2$; + 两端点度数不为 $2$; 每条链包含端点。度数指原图上度数。 先做 $k=1$。 最终树的结构满足: 1. 每条极长链最多包含两个分叉点,分别位于两端。 2. 每个分叉点至少连接三条极长链。 任选原树的一个叶子作为根。定义 $f_{u, j}$ 表示:处理完 $u$ 的子树后,继续向父亲方向延伸的链中,包含 $j$ 个分叉点的方案数。这部分树上背包即可。 对于一个节点 $u$: #### 不是分叉点 开临时数组 $a_j$ 记录已处理的儿子合并后,链里有 $j$ 个分叉点的方案数。 初始 $a_0=1$,$a_1=a_2=0$。再开一个数组 $c$ 转移: $$c_{i+j} \leftarrow c_{i + j} + a_i f_{v, j}$$ 合并完把 $c$ 赋给 $a$。 最后: $$f_{u,j} \leftarrow f_{u, j} + a_j$$ #### 是分叉点 需要将儿子分开来: + 状态为 $2$ 的儿子不能转移上来。 + 状态为 $1$ 的儿子不能和状态 $1$ 的儿子合并。 + 状态为 $0$ 的儿子可以任意合并。 用 $b_m$ 表示恰好 $m$ 个儿子是状态 $1$,其余儿子都是 $0$ 的方案数。初始化为 $b_0=1$。 开一个新数组 $c$ 处理转移: $$c_m \leftarrow c_m + b_m f_{v, 0}$$ $$c_{m+1} \leftarrow c_{m+1} + b_m f_{v, 1}$$ 之后把 $c$ 赋给 $b$。 再定义 $B_{n, m}$ 表示将 $n$ 个不同元素分组,其中已经有了 $m$ 个有区别、允许为空的组,还允许新建任意个无区别的非空新组。 那么 $B_{0, j} = 1$,$B_{i, j} = jB_{i - 1, j} + B_{i - 1, j + 1}$。 怎么得到这个转移呢: 指定一个元素所在的组,如果在已有组有 $j$ 种选择,或者在新组,可以把这个组看成一个额外的已有组。 回到树形 dp 上。设有 $k$ 个 $0$ 儿子。继续分类讨论: 1. 父亲不和 $1$ 儿子在一组,转移为 $f_{u, 1} \leftarrow f_{u, 1} + b_m(B_{k,m+1} - [m \leq 1] 2^k)$; 2. 父亲和 $1$ 儿子在一组,转移为 $f_{u,2}\leftarrow f_{u,2}+mb_m(B_{k,m}-[m \leq 2]2^k)$。 设根为 $r$,最终答案即为 $f_{r,0}+f_{r,1}+f_{r,2}$。 那,多棵树怎么做呢? 选第一棵树为参照树,用并查集维护它的边,处理其它树的约束,记录哪些边必须在一条极长链里。dp 的时候把整个边组合并在一起做就可以了。 到这里,这个题就做完啦! ::::info[代码] ```cpp #include<bits/stdc++.h> #define int long long using namespace std; const int mod = 998244353; int p, n, k, dep[5005], fa[15][5005], pw[5005]; int B[5005][5005], f[5005][3], g[5005][3]; vector<int> G[5005], bel[5005], vec[5005], tmp; bitset<5005> H[5005], h[5005], ttmp, S, T; struct DSU{ int f[5005]; void ini(){ for (int i = 1; i <= n; i++) f[i] = i; } int find(int x){ return f[x] == x ? x : f[x] = find(f[x]); } int merge(int u, int v){ int fu = find(u); int fv = find(v); if (dep[fu] > dep[fv]) swap(fu, fv); f[fv] = fu; return fu; } }desu; int qpow(int x, int k){ int ans = 1; while (k){ if (k & 1) ans = ans * x % mod; x = x * x % mod; k >>= 1; } return ans; } void ini(){ pw[0] = 1; for (int i = 1; i <= n; i++) pw[i] = pw[i - 1] * 2 % mod; for (int i = 0; i <= n; i++) B[0][i] = 1; for (int i = 1; i <= n; i++) for (int j = 0; i + j <= n; j++) B[i][j] = (j * B[i - 1][j] + B[i - 1][j + 1]) % mod; } void dfs(int u, int fat){ fa[0][u] = fat; dep[u] = dep[fat] + 1; for (auto v : G[u]) if (v != fat) dfs(v, u); } void inifa(){ for (int i = 1; i <= 14; i++) for (int j = 1; j <= n; j++) fa[i][j] = fa[i - 1][fa[i - 1][j]]; } int lca(int u, int v){ if (dep[u] < dep[v]) swap(u, v); for (int i = 14; ~i; i--) if (dep[fa[i][u]] >= dep[v]) u = fa[i][u]; if (u == v) return u; for (int i = 14; ~i; i--) if (fa[i][u] != fa[i][v]) u = fa[i][u], v = fa[i][v]; return fa[0][u]; } int pmerge(int u, int v, int r){ int w = lca(u, v); vector<int> vec; while (dep[u] > dep[w]){ int t = desu.find(u); vec.emplace_back(t); u = fa[0][t]; } while (dep[v] > dep[w]){ int t = desu.find(v); vec.emplace_back(t); v = fa[0][t]; } for (auto x : vec) r = (!r ? x : desu.merge(r, x)); return r; } void dp(int u, int fa); void ddfs(int u, int fa){ g[u][0] = 1; for (auto v : vec[u]){ if (v == fa) continue; dp(v, u); int a[3] = {0, 0, 0}; for (int i : {0, 1, 2}) for (int j = 0; i + j <= 2; j++) a[i + j] = (a[i + j] + 1ll * g[u][i] * f[v][j]) % mod; for (int i : {0, 1, 2}) g[u][i] = a[i]; } } void dp(int u, int fa){ int cnt = bel[u].size() - (bool)fa; int b[5005]; for (int i = 1; i <= cnt; i++) b[i] = 0; b[0] = 1; int a[3] = {1, 0, 0}, ccnt = 0; for (auto v : bel[u]){ if (v == fa) continue; ddfs(v, u); int c[3] = {}; for (int i : {0, 1, 2}) for (int j = 0; i + j < 3; j++) c[i + j] = (c[i + j] + a[i] * g[v][j]) % mod; for (int i : {0, 1, 2}) a[i] = c[i]; ++ccnt; for (int i = ccnt; i >= 0; i--) b[i] = (b[i] * g[v][0] + (i ? b[i - 1] * g[v][1] : 0)) % mod; } for (int i : {0, 1, 2}) f[u][i] = a[i]; for (int i = 0; i <= cnt; i++){ int k = cnt - i, x = B[k][i + 1]; if (i <= 1) x = (x - pw[k] + mod) % mod; f[u][1] = (f[u][1] + b[i] * x) % mod; if (i){ x = B[k][i]; if (i <= 2) x = (x - pw[k] + mod) % mod; f[u][2] = (f[u][2] + i * b[i] % mod * x) % mod; } } } signed main(){ #ifndef ONLINE_JUDGE clog << unitbuf; #endif ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); cin >> p >> k >> n; if (p == 1){ for (int i = 1; i <= n; i++) H[i].set(); for (int _ = 1; _ <= k; _++){ for (int i = 1; i <= n; i++){ G[i].clear(); h[i].reset(); } for (int i = 1; i < n; i++){ int u, v; cin >> u >> v; G[u].emplace_back(v); G[v].emplace_back(u); } for (int i = 1; i <= n; i++){ if (G[i].size() == 2) continue; for (auto v : G[i]){ vector<int> vec; vec.emplace_back(i); vec.emplace_back(v); int u = i; while (G[v].size() == 2){ int w = G[v][0] ^ G[v][1] ^ u; u = v; v = w; vec.emplace_back(v); } ttmp.reset(); for (auto x : vec) ttmp.set(x); for (auto x : vec) h[x] |= ttmp; } } for (int i = 1; i <= n; i++){ H[i] &= h[i]; H[i].reset(i); } } int ans = 1; for (int i = 1; i <= n; i++){ while (H[i].count()){ int v = H[i]._Find_first(); S = H[i] & H[v]; S.set(i); S.set(v); int m = S.count(); ans = ans * qpow(m, m - 2) % mod; for (int u = S._Find_first(); u <= n; u = S._Find_next(u)) H[u] &= ~S; } } cout << ans; exit(0); } desu.ini(); ini(); for (int _ = 1; _ <= k; _++){ for (int i = 1; i <= n; i++){ G[i].clear(); } for (int i = 1; i < n; i++){ int u, v; cin >> u >> v; G[u].emplace_back(v); G[v].emplace_back(u); } if (_ == 1){ dfs(1, 0); inifa(); } for (int i = 1; i <= n; i++){ if (G[i].size() == 2) continue; for (auto v : G[i]){ vector<int> vec; vec.emplace_back(i); vec.emplace_back(v); int u = i; while (G[v].size() == 2){ int w = G[v][0] ^ G[v][1] ^ u; u = v; v = w; vec.emplace_back(v); } int r = 0; for (int j = 1; j < vec.size(); j++) r = pmerge(vec[j - 1], vec[j], r); } } } for (int i = 2; i <= n; i++){ int u = desu.find(i); vec[u].emplace_back(i); vec[u].emplace_back(fa[0][i]); } for (int i = 1; i <= n; i++){ sort(vec[i].begin(), vec[i].end()); vec[i].resize(unique(vec[i].begin(), vec[i].end()) - vec[i].begin()); for (auto u : vec[i]) bel[u].emplace_back(i); } int r = 1; while (bel[r].size() > 1) r++; dp(r, 0); cout << (f[r][0] + f[r][1] + f[r][2]) % mod; return 0; } ``` ::::