1 条题解
-
0
「POI2017 R1」破坏 Sabotage 题解
问题深入分析
我们有一个树形组织,根节点为1号(最高领导)。关键规则如下:
叛乱传播机制:
- 某个员工作为破坏者率先叛乱(但不强迫下属)
- 对于每个员工u:如果u的直接下属中叛乱的比例 > x,则u也会叛乱
- u叛乱后,会强迫所有直接和间接下属跟随叛乱
- 叛乱只向上传播,不会向下或横向传播
目标:找到最小的x,使得任意员工作为破坏者时,总叛乱人数 ≤ k
关键性质与观察
1. 单调性分析
- x ↗ → 叛乱传播条件更严格 → 最大叛乱人数 ↘
- x ↘ → 叛乱传播条件更宽松 → 最大叛乱人数 ↗
- 因此可以使用二分答案
2. 叛乱传播的数学表达
对于节点u,设:
children[u]
:u的直接下属集合rebel_children
:u的直接下属中叛乱的数量
叛乱条件:
rebel_children / |children[u]| > x
即:
rebel_children > x * |children[u]|
由于人数是整数,等价于:
rebel_children ≥ ⌊x * |children[u]|⌋ + 1
3. 问题转化
对于给定的x,我们需要检查:是否存在某个破坏者v,使得引发的总叛乱人数 > k
算法设计
方法一:树形DP + 二分答案(推荐)
状态定义
size[u]
:以u为根的子树大小dp[u]
:如果u叛乱,最少需要多少个直接下属已经叛乱
状态转移
对于叶子节点:
dp[u] = 1
(没有下属,条件自动满足)对于非叶子节点:
dp[u] = ⌈x * |children[u]|⌉
即:需要至少
dp[u]
个直接下属叛乱,u才会叛乱检查函数设计
bool check(double x) { // 计算每个节点的阈值 for (int u = n; u >= 1; u--) { if (children[u].empty()) { dp[u] = 1; // 叶子节点 } else { dp[u] = ceil(x * children[u].size()); } } // 检查每个可能的破坏者 for (int v = 1; v <= n; v++) { int total = count_rebellion(v, x); if (total > k) return false; } return true; }
叛乱人数计算
从破坏者v开始:
- v率先叛乱
- 向上遍历祖先,检查每个祖先u:
- 如果u的已叛乱直接下属数量 ≥ dp[u],则u叛乱
- 继续向上检查
- 统计所有叛乱节点
方法二:优化算法
观察到:对于破坏者v,叛乱传播路径是从v到根的链上的连续一段。
我们可以预处理:
up[u]
:从u开始向上,最多能传播到哪个祖先- 然后O(1)计算每个破坏者的影响范围
复杂度分析
- 二分答案:O(log(1e8)) ≈ 27次
- 每次检查:O(n) 计算dp值 + O(n) 检查每个破坏者
- 总复杂度:O(n log(1/ε)),满足n ≤ 5e5
实现细节
1. 树结构存储
vector<int> children[MAXN]; int parent[MAXN], size[MAXN];
2. 预处理
void dfs(int u) { size[u] = 1; for (int v : children[u]) { dfs(v); size[u] += size[v]; } }
3. 二分精度控制
double l = 0, r = 1; for (int iter = 0; iter < 50; iter++) { double mid = (l + r) / 2; if (check(mid)) r = mid; else l = mid; }
样例详细分析
输入:
9 3 1 1 2 2 2 3 7 3
树结构:
1 / \ 2 3 /|\ |\ 4 5 6 7 9 | 8
当x = 0.666...时:
- 节点3有2个直接下属(7,9),阈值 = ceil(0.666×2) = 2
- 节点7有1个直接下属(8),阈值 = ceil(0.666×1) = 1
破坏者8号:
- 8叛乱 → 7有1/1=100%下属叛乱 > 0.666 → 7叛乱
- 7叛乱 → 3有1/2=50%下属叛乱 ≤ 0.666 → 停止传播
- 总叛乱:3,7,8,9 → 4人 > k=3
因此需要x ≥ 2/3。
边界情况处理
- k = 1:x = 1.0(只有破坏者本人叛乱)
- k = n:x = 0.0(允许所有人叛乱)
- 链状树:传播更容易,需要更大的x
- 星形树:根节点需要很多下属叛乱才会加入
总结
本题的关键在于:
- 理解叛乱传播的数学条件
- 将连续变量x的求解转化为二分答案问题
- 设计高效的检查函数,利用树形DP计算传播范围
- 注意整数人数与实数比例之间的转换
通过二分搜索士气值x,并在每次检查中模拟最坏情况的破坏者,我们能够高效地找到满足条件的最小x值。
- 1
信息
- ID
- 3157
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 4
- 已通过
- 1
- 上传者