CF671D Roads in Yusland 题解

作者: xht37 分类: 题解 发布时间: 2020-03-01 02:21

Visits: 231

CF671D Roads in Yusland

题意

  • 给定一棵 $n$ 个点的以 $1$ 为根的树。
  • 有 $m$ 条路径 $(x,y)$,保证 $y$ 是 $x$ 的祖先,每条路径有一个权值。
  • 你要在这些路径中选择若干条路径,使它们能覆盖每条边,同时权值和最小。
  • $n,m \le 3 \times 10^5$。

题解

设 $f_i$ 表示覆盖 $i$ 子树内的所有边和 $i$ 与其父亲的边的最小权值,则答案就是 $\sum_{x \in \operatorname{son}(1)} f_x$。

然而并不是所有 $f$ 都是最优的,对于一种覆盖 $i$ 子树内的所有边和 $i$ 与其父亲的边的方案,可能它并不是最小权值,但是它向上延伸得更长。

因此我们要保留住所有可能最优的方案,同时要计算其中的最小值,这启发我们考虑小根堆。具体来说,对于节点 $i$,$i$ 上的小根堆内存储了覆盖 $i$ 子树内的所有边和 $i$ 与其父亲的边的所有可能最优的方案。

考虑如何转移,即当 $x \in \operatorname{son}(y)$ 时,如何将 $x$ 上的方案转移到 $y$ 上。对于一个权值为 $k$ 的方案:

  • 如果向上延伸超过了 $y$,则这种方案在 $y$ 的小根堆里权值应该为 $k + \sum_{z \in \operatorname{son}(y)} f_z – f_x$。
  • 如果正好向上延伸到 $y$,则这种方案不应该出现在 $y$ 上,需要被扔掉。

这时候我们发现,一个点有来自多个儿子的小根堆,显然我们需要将它们合并起来,于是将小根堆改为左偏树即可。

同时,我们还需要支持整棵左偏树加上一个定值,打个标记即可。

对于需要扔掉的方案,由于我们不需要实时删除,因此考虑我们暂时先保留它们,当它们成为根时再弹出即可。

设 $n,m$ 同阶,总时间复杂度为 $\mathcal O(n \log n)$。

代码

#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;
}

发表评论

电子邮件地址不会被公开。 必填项已用*标注