虚树总结
引入
在做一些树上问题的时候,题目会给出一些 关键点,所有的问题只跟关键点的关系很大,而跟其他的点关系不大,这时候我们可以考虑 虚树。
大工程:
在一场战争中,战场由 \(n\) 个岛屿和 \(n-1\) 个桥梁组成,保证每两个岛屿间有且仅有一条路径可达。现在,我军已经侦查到敌军的总部在编号为 \(1\) 的岛屿,而且他们已经没有足够多的能源维系战斗,我军胜利在望。已知在其他 \(k\) 个岛屿上有丰富能源,为了防止敌军获取能源,我军的任务是炸毁一些桥梁,使得敌军不能到达任何能源丰富的岛屿。由于不同桥梁的材质和结构不同,所以炸毁不同的桥梁有不同的代价,我军希望在满足目标的同时使得总代价最小。
侦查部门还发现,敌军有一台神秘机器。即使我军切断所有能源之后,他们也可以用那台机器。机器产生的效果不仅仅会修复所有我军炸毁的桥梁,而且会重新随机资源分布(但可以保证的是,资源不会分布到 \(1\) 号岛屿上)。不过侦查部门还发现了这台机器只能够使用 \(m\) 次,所以我们只需要把每次任务完成即可。
朴素做法
下面称这 \(k\) 座岛屿为关键点。
先考虑朴素 \(dp\)。
设 \(f_i\) 表示使 \(i\) 的子树不与 \(1\) 连通最小的代价。
枚举 \(u\) 的儿子 \(v\)。
若 \(u\) 不是关键点:\(f_u = \sum_{v} \min(dp_v, w(u, v))\)。
否则: \(f_u = \sum_v w(u, v)\)。
我们考虑一条路径 \(s \to t\)(\(s, t\) 不是父子)中 \(s, t\) 是关键点, \(s \to t\) 的路径上的子树里没有其他关键点。
那么,转移式就变为 \(min(f_s, \min_{e \in {s\to t}}{w(e)})\)。
因此我们需要 浓缩信息,把一整颗大树浓缩成一颗小树。
构造虚树
最开始有序列 \(A\),包含所有关键点
将所有关键点按照 \(dfs\) 序从小到大排序,相邻两点求 \(Lca\),加入 \(A\) 中。
再将 \(A\) 中所有点从小大排序,并去重。
再枚举相邻两点 \(x,y\),求 \(Lca\),再连边 \(Lca, y\)。
具体证明可以看 虚树 - OI Wiki
边权要根据题目而定。
对本题而言,边权就是两点在原树上的路径最小值。
#include <bits/stdc++.h>
using namespace std;
//#define int long long
const int N = 2.5e5 + 10;
vector<pair<int, int> > edge[N];
int fa[N][21], mn[N][21], dep[N], lg2[N], dfn[N], qwq;
void dfs1(int u, int f) {
fa[u][0] = f, dep[u] = dep[f] + 1, dfn[u] = ++qwq;
for(int i = 1; i <= 19; i++) fa[u][i] = fa[fa[u][i - 1]][i - 1], mn[u][i] = min(mn[u][i - 1], mn[fa[u][i - 1]][i - 1]);
for(auto y : edge[u]) {
int v = y.first, w = y.second;
if(v == f) continue;
mn[v][0] = w;
dfs1(v, u);
}
}
pair<int, int> Lca(int u, int v) {
if(dep[u] < dep[v]) swap(u, v);
int res = 1e9 + 10;
while(dep[u] > dep[v]) res = min(res, mn[u][lg2[dep[u] - dep[v]]]), u = fa[u][lg2[dep[u] - dep[v]]];
if(u == v) return {u, res};
for(int i = 19; i >= 0; i--) {
if(fa[u][i] != fa[v][i]) res = min({res, mn[u][i], mn[v][i]}), u = fa[u][i], v = fa[v][i];
}
return {fa[u][0], min({res, mn[u][0], mn[v][0]})};
}
int a[N * 2], m, vis[N];
long long dp[N];
vector<pair<int, int> > tree[N];
void dfs(int u, int f) {
dp[u] = 0;
for(auto y : tree[u]) {
int v = y.first, w = y.second;
if(v == f) continue;
dfs(v, u);
if(vis[v]) dp[u] += 1ll * w;
else dp[u] += min(dp[v], 1ll * w);
}
}
signed main() {
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(0);
int n;
cin >> n;
for(int i = 1; i < n; i++) {
int u, v, w; cin >> u >> v >> w;
edge[u].push_back({v, w});
edge[v].push_back({u, w});
}
for(int i = 0; i <= 19; i++) mn[0][i] = mn[0][i] = 1e9 + 10;
for(int i = 1; i <= n; i++) lg2[i] = log2(i);
dfs1(1, 0);
int T; cin >> T;
while(T--) {
cin >> m;
for(int i = 1; i <= m; i++) cin >> a[i], vis[a[i]] = 1;
a[++m] = 1;
sort(a + 1, a + m + 1, [&](int x, int y) {return dfn[x] < dfn[y];});
int t = m;
for(int i = 1; i < t; i++) {
a[++m] = Lca(a[i], a[i + 1]).first;
}
sort(a + 1, a + m + 1, [&](int x, int y) {return dfn[x] < dfn[y];});
m = unique(a + 1, a + m + 1) - a - 1;
for(int i = 1; i < m; i++) {
auto p = Lca(a[i], a[i + 1]);
tree[p.first].push_back({a[i + 1], Lca(p.first, a[i + 1]).second});
}
// 构造虚树
dfs(1, 0);
for(int i = 1; i <= m; i++) tree[a[i]].clear(), vis[a[i]] = 0;
cout << dp[1] << "\n";
}
return 0;
}

浙公网安备 33010602011771号