更为简单的做法
西江月·定风波
·
·
题解
前一半是我自己做的,所以是一个 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;
}
```
::::