专栏文章
题解:P11322 [NOISG 2020 Qualification] Firefighting
P11322题解参与者 2已保存评论 1
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 1 条
- 当前快照
- 1 份
- 快照标识符
- @mipxaded
- 此快照首次捕获于
- 2025/12/03 19:27 3 个月前
- 此快照最后确认于
- 2025/12/03 19:27 3 个月前
Statement
给定一棵树,边有边权。选出最少的关键节点建消防站,使得每个节点到最近的消防站的距离不大于 。
Sol(greedy,dp)
注意到这道题就是有边权的,不用二分答案的,需要输出方案的 P3523。
具体地,我们有一个贪心思路,就是在能够满足子树中最深的节点能够被覆盖的情况下,将消防站建在尽量靠近根节点,也就是更浅的位置。
但是这样子暴力做是 的,我们可以用 dp 进行优化。具体地,用 维护子树内最浅的消防站(若无,可以视作在无穷远处有一个消防站)的深度, 维护子树内最深的坏节点(目前没有被消防站保护的节点)的深度。转移直接对儿子求 min/max 就可以。
如果我们发现最深的坏节点 可以被另一颗子树的消防站保护,那么子树 内所有的坏节点一定可以被保护。这是我们可以直接删除 。
如果我们发现最深的坏节点 到达当前节点 的父亲的距离大于 了(或者 是根节点,但是仍然有坏节点没有被保护),我们就无法把保护这个坏节点的责任推给父亲,因此当前节点必须建立消防站。
如果这两种情况都没有发生,就什么都不做。
然后就做完了。
Code
CPP#include<iostream>
#include<algorithm>
#include<vector>
#include<numeric>
using namespace std;
const int _= 3.1e5;
#define CMP(X) [&](int cu, int cv){return X;}
int n, ui, vi, fa[_];
long long dep[_], f[_], g[_], k, di; //警钟敲烂
vector<pair<int, int> > e[_];
vector<int> ans, ord;
void dfs(int o){
for(auto eg : e[o]) if(eg.first != fa[o]){
dep[eg.first] = dep[o] + eg.second;
fa[eg.first] = o;
dfs(eg.first);
}
}
signed main(){
ios::sync_with_stdio(0),
cin.tie(0), cout.tie(0);
cin >> n >> k;
ord.resize(n), iota(ord.begin(), ord.end(), 1);
for(int i = 1; i < n; i++){
cin >> ui >> vi >> di;
e[ui].push_back(make_pair(vi, di));
e[vi].push_back(make_pair(ui, di));
}
dep[1] = 0, fa[1] = 1, dfs(1);
sort(ord.begin(), ord.end(), CMP(dep[cu] > dep[cv]));
for(int o: ord){
f[o] = 2e18, g[o] = dep[o]; // 注意这里直接维护深度
for(auto ex : e[o]) if(ex.first != fa[o]){
if(f[ex.first] < f[o]) f[o] = f[ex.first];
if(g[ex.first] > g[o]) g[o] = g[ex.first];
}
if(g[o] + f[o] - 2 * dep[o] <= k) g[o] = -1e17;
else if(g[o] - dep[fa[o]] > k || (o == 1 && g[o] >= 0) )
f[o] = dep[o], g[o] = -1e17, ans.push_back(o);
}
cout << ans.size() << endl;
for(int&ax : ans){
cout << ax << " ";
}
}
相关推荐
评论
共 1 条评论,欢迎与作者交流。
正在加载评论...