0%

Hello 2026——D2. Tree Coloring (Hard Version)(随机化算法可以分层实现)(猜测答案时,可以先猜测一个下界然后尝试不断提升)

题目大意

问题描述:

  • 给定一棵有 n 个顶点的有根树,根节点为 1,所有顶点初始为白色

  • 定义 $d_i $ 为根节点第 i 个顶点的距离

操作规则:

  • 每次操作可以选择一个白色顶点的子集 S

  • S 中的任意两个节点必须满足:

    • 不通过边相连(不相邻)
    • 到根节点的距离不相同(不在同一层)
  • 将 S 中的所有顶点涂成黑色

目标:

  • 求将整棵树全部涂成黑色所需的最少操作次数

数据范围:

  • 测试用例数 t: 1 ≤ t ≤ 10⁴

  • 顶点数 n: 2 ≤ n ≤ 2×10⁵

  • 所有测试用例的 n 之和不超过 2×10⁵

版本说明:

  • 这是简化版本,只需要输出最少操作次数

  • 困难版本(D2)需要输出具体的操作方案

启示:当我们发现一道题目需要猜结论,但是我们没啥思路的时候,不妨先猜下界,然后想办法不断提升这个下界(特别是对于这种个数推断与猜测)。

思路讲解

easy version,我们想要搞一个这个 dp,但是就出现这个问题了,因为和别的子树的交互,删除,没有考虑到,这个是树上 dp 的弱点,要么想办法在这个转移/合并的时候解决这个问题,要么就换做法。

发现这个 easy version 过题比较多,那么就要想到,可能就是猜结论了,不过直接猜出来比较难,我们可以先猜猜下界。首先,设 tit_i 为第 ii 层的节点个数,那么 ansmax(t1,,tn)ans≥\text{max}(t_1,\cdots,t_n) 是肯定的,不过,如果说 ii 层的这个所有节点都共用一个根节点,这个时候还需要对 ti+1t_i+1 取 max 值。(这个就是 easy 版本的结论)

那么 hard 版本就相当于构造性证明这个结论。

不过,想办法构造比较困难,我们不如交给随机化算法。即,我们的这个构造实际上可以看作为给这个节点涂颜色,要求节点颜色种类不能超过 ansans,并且和父节点颜色不同。我们仅仅只是使用一个这个 holeColorholeColor 的贪心进行一个瞎搞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
for (int i=1;i<=mx_dep;++i) {
while (true) {
shuffle(all(dep_node_mtx[i]),rng);
ll holeColor=-1;
ll cur_color=1;
for (auto u:dep_node_mtx[i]) {
if (holeColor!=-1 && holeColor!=color[parent[u]]) {
color[u]=holeColor;
holeColor=-1;
continue;
}
if (cur_color!=color[parent[u]]) {
color[u]=cur_color;
cur_color++;
}else {
holeColor=cur_color;
cur_color++;
color[u]=cur_color;
cur_color++;
}
}
if (cur_color<=ans+1) {
break;
}
}
}

AC代码

AC
https://codeforces.com/contest/2183/submission/358257705

心路历程(WA,TLE,MLE……)

走了一些弯路,做这种图论的题目,还是需要更有整体意识

顺序会影响结果的原因很朴素:这段构造是“局部贪心”,它不会为后面的点“预留”某个关键编号

同一层里每个点只禁用 1 个编号(父亲的 du[parent]),但“哪个点拿走哪个编号”是有讲究的;你现在这套贪心只维护 一个洞 g,一旦把洞用在了“本来很灵活的点”身上,就可能把后面那个“更挑剔的点”逼到只能用更大的编号,最后 > ans 失败。

我用一个最小反例把它讲清楚(这就是“为什么顺序影响结果”的精髓):


反例:明明有解,但某个顺序会让贪心爆掉

设这一层有 3 个点 (A,B,C),D1 算出来最优 (K=ans=3)。

它们父亲的编号分别是:

  • (du[parent(A)] = 1) ⇒ (A) 不能用 1

  • (du[parent(B)] = 2) ⇒ (B) 不能用 2

  • (du[parent©] = 3) ⇒ © 不能用 3

显然存在合法分配(且都在 1…3 内):

du[A]=2, du[B]=3, du[C]=1du[A]=2,\ du[B]=3,\ du[C]=1

都互不相同,也都避开了父亲的编号。


但你的贪心在顺序 A→B→C 时会失败

你的规则(对应代码)是:

  • t:按 1,2,3… 递增发新编号

  • 如果当前点禁用 t,就“跳过”它:把 t 存到洞 g,然后给它 t+1

  • g 存在且当前点不禁用 g,就先把洞 g 填给它

现在按顺序 A, B, C 跑:

处理 A(禁 1)

  • t=1,但 A 禁 1 ⇒ 产生洞 g=1t 再加到 2

  • 给 A:du[A]=2

处理 B(禁 2)

  • 此时 g=1,而 B 的父亲编号是 2,不等于 1 ⇒ 贪心立刻把洞填掉

  • 给 B:du[B]=1,洞清空

处理 C(禁 3)

  • 现在已用掉 {2,1},t 会走到 3

  • 但 C 禁 3 ⇒ 贪心只能跳过 3,给它 4

  • 得到 du[C]=4 > ans(=3)这层构造失败,重来

所以你看到的现象就是:同样的一层,同样的 K,有的顺序一把过,有的顺序必炸。