【题解】CF671D-Roads in Yusland
给定一棵以 \(1\) 为根的树,给定 \(m\) 条路径,每条路径覆盖 \(x_i\) 到 \(y_i\),权值为 \(w_i\),保证 \(y_i\) 是 \(x_i\) 祖先,求覆盖整棵树的最小路径权值和。
首先我们不难想出一个一个树形 DP,用 \(f_{x,d}\) 表示覆盖了 \(x\) 的子树,并且向上衍生到深度 \(d\) 的答案。根据套路我们可以直接线段树优化 DP,时空复杂度是 \(\mathcal{O}(n\log n)\),空间可能有点卡。
我们可以换一种方式维护。对于每个 \(x\),开一个堆维护二元组 \(\{value,dep\}\) 表示有一个代价为 \(value\) 的 \(f_{x,d}\) 方案。合并子树时,只用支持整体加即可。启发式合并可以做到时空复杂度 \(\mathcal{O}(m\log ^2 m + n) - \mathcal{O}(n + m)\)。用左偏树可以做到 \(\mathcal{O}(m \log m + n) - \mathcal{O}(n + m)\)。
#define N 300005
int n, m, d[N]; LL f[N], p[N];
vector<int>e[N];
vector<Pr> c[N];
struct node{
LL val; int dep;
bool operator<(const node o)const{return val < o.val;}
};
multiset<node>s[N];
void merge(int x,int y){
if(si(s[x]) < si(s[y]))swap(s[x], s[y]), swap(p[x], p[y]);
go(w, s[y])s[x].insert(node{w.val + p[y] - p[x], w.dep});
s[y].clear();
}
void check(int x){
while(!s[x].empty() && (*s[x].begin()).dep >= d[x])s[x].erase(s[x].begin());
}
void dfs(int x,int fa){
d[x] = d[fa] + 1;
go(y, e[x])if(y != fa)dfs(y, x), f[x] += f[y];
go(y, e[x])if(y != fa)p[y] += f[x] - f[y], merge(x, y);
go(y, c[x])s[x].insert(node{y.se + f[x] - p[x], d[y.fi]});
check(x); if(s[x].empty()){puts("-1"); exit(0);}
f[x] = (*s[x].begin()).val + p[x];
}
int main() {
read(n, m);
rp(i, n - 1){
int x, y; read(x, y);
e[x].pb(y), e[y].pb(x);
}
while(m--){
int x, y, z; read(x, y, z);
c[x].pb(Pr{y, z});
}
LL ans = 0;
go(y, e[1])dfs(y, 1), ans += f[y];
cout << ans << endl;
return 0;
}