题解 CF671D 【Roads in Yusland】
CF671D Roads in Yusland
题意
- 给定一棵
n 个点的以1 为根的树。 - 有
m 条路径(x,y) ,保证y 是x 的祖先,每条路径有一个权值。 - 你要在这些路径中选择若干条路径,使它们能覆盖每条边,同时权值和最小。
-
题解
设
然而并不是所有
因此我们要保留住所有可能最优的方案,同时要计算其中的最小值,这启发我们考虑小根堆。具体来说,对于节点
考虑如何转移,即当
- 如果向上延伸超过了
y ,则这种方案在y 的小根堆里权值应该为k + \sum_{z \in \operatorname{son}(y)} f_z - f_x 。 - 如果正好向上延伸到
y ,则这种方案不应该出现在y 上,需要被扔掉。
这时候我们发现,一个点有来自多个儿子的小根堆,显然我们需要将它们合并起来,于是将小根堆改为左偏树即可。
同时,我们还需要支持整棵左偏树加上一个定值,打个标记即可。
对于需要扔掉的方案,由于我们不需要实时删除,因此考虑我们暂时先保留它们,当它们成为根时再弹出即可。
设
代码
#define Fail print(-1), exit(0)
const int N = 3e5 + 7;
int n, m, d[N], tot, rt[N];
ll f[N], ans;
vi e[N];
vector<pi> p[N];
struct T {
pair<ll, int> x;
ll z;
int l, r, f, d;
} t[N];
inline void add(int x, ll k) {
if (x) t[x].x.fi += k, t[x].z += k;
}
inline void spd(int x) {
if (t[x].z) add(t[x].l, t[x].z), add(t[x].r, t[x].z), t[x].z = 0;
}
int merge(int x, int y) {
if (!x || !y) return x | y;
if (t[x].x > t[y].x) swap(x, y);
spd(x);
t[t[x].r=merge(t[x].r,y)].f = x;
if (t[t[x].r].d > t[t[x].l].d) swap(t[x].l, t[x].r);
t[x].d = t[t[x].r].d + 1;
return x;
}
void dfs(int x, int fa) {
d[x] = d[fa] + 1;
for (pi o : p[x])
t[++tot].x = o, t[tot].d = 1, rt[x] = merge(rt[x], tot);
ll s = 0;
for (int y : e[x])
if (y != fa) {
dfs(y, x), s += f[y];
add(rt[y], -f[y]), rt[x] = merge(rt[x], rt[y]);
}
add(rt[x], s);
while (rt[x] && d[t[rt[x]].x.se] >= d[x])
spd(rt[x]), rt[x] = merge(t[rt[x]].l, t[rt[x]].r);
if (!rt[x]) Fail;
f[x] = t[rt[x]].x.fi;
}
int main() {
rd(n), rd(m);
for (int i = 1, x, y; i < n; i++)
rd(x), rd(y), e[x].pb(y), e[y].pb(x);
for (int i = 1, x, y, z; i <= m; i++)
rd(x), rd(y), rd(z), p[x].pb(mp(z, y));
d[1] = 1;
for (int x : e[1]) dfs(x, 1), ans += f[x];
print(ans);
return 0;
}