题解 CF191C Fools and Roads

题目简述

给定一棵 \(n\) 个节点的树,最开始所有边的边权为 \(0\) ,有 \(m\) 次操作,每次操作会给定两个点,把这两个点的路径上的所有的权值 \(+1\) ,操作完成后,输出所有的边的权值。

\(2 \leq n \leq 10^5 ,0 \leq m \leq 10^5\)

保证每次操作的两个点不是同一个点。

Solution

下面用 \(lca(u,v)\) 表示 \(u,v\) 的最近公共祖先, \(fa_x\) 表示 \(x\) 的父节点。

看到链 \(+1\) ,又是统一输出答案,那肯定是树上差分。

首先把边权转换到点上面去:因为每个点只有一个父亲,所以每个点到父亲的边是唯一确定的,因此我们可以把每个点到父亲的这条边的权值记录到这个点上去。

然后就变成了把一条路径中的除去最高点的所有点的点权 \(+1\)

简单说一下树上差分是怎么做的:我们用一个 \(w_i\) 来表示 \(i\) 到根路径上的所有点的权值都应该要加上 \(w_i\) ,最后一个点真正的权值就是这个点子树的所有节点的权值和(这个点的子树里的所有点到根的路径一定经过了当前这个点,因为当前这个点是子树的根节点)。

然后把把 \(u\)\(v\) 路径上的所有点权值 \(+1\) 拆开,变成让 \(u\) 到根所有点的权值 \(+1\)\(v\) 到根所有点的权值 \(+1\) ,然后 \(w_{lca(u,v)} \gets w_{lca(u,v)} - 2\) ,也就是说, \(lca(u,v)\) 到根上的所有点的权值都被多加了两次,我们要把这两次减掉

注意:这里的修改的点并不包括 \(w_{lca(u,v)}\) ,因为这里是边权,而 \(lca(u,v)\)\(fa_{lca(u,v)}\) 的边不在 \(u\to v\) 上,所以不用加。

如果题目本身给出的就是点权,那么 \(w_{lca(u,v)}\) 本来应该加一次,现在加了两次,应该减掉一次,也就是 \(w_{lca(u,v)} \gets w_{lca(u,v)} - 1\) 然后 \(fa_{lca(u,v)}\) 到根的所有点的权值都被多加了两次 ,应该让 \(w_{fa_{lca(u,v)}} \gets w_{fa_{lca(u,v)}} - 2\)对边进行修改和对点进行修改是不一样的。如果不能理解可以画出图来,就能明白了。

这样的话,复杂度为 \(\mathcal{O}(m \log n)\) (假设使用的是 \(\mathcal{O}(n)-\mathcal{O}(\log n)\)\(lca\) )。

代码如下:

#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <iostream>
#include <queue>
using namespace std;
inline int read() {
    int num = 0 ,f = 1; char c = getchar();
    while (!isdigit(c)) f = c == '-' ? -1 : f ,c = getchar();
    while (isdigit(c)) num = (num << 1) + (num << 3) + (c ^ 48) ,c = getchar();
    return num * f;
}
const int N = 1e5 + 5 ,M = 2e5 + 5;
struct Edge {
    int to ,next;
    Edge (int to = 0 ,int next = 0) :
        to(to) ,next(next) {}
}G[M]; int head[N] ,cnt;
inline void add(int u ,int v) {
    G[++cnt] = Edge(v ,head[u]); head[u] = cnt;
    G[++cnt] = Edge(u ,head[v]); head[v] = cnt;
}
int fa[N] ,son[N] ,siz[N] ,dep[N] ,top[N] ,a[N];
inline void dfs1(int now) {
    dep[now] = dep[fa[now]] + 1;
    siz[now] = 1;
    for (int i = head[now]; i ; i = G[i].next) {
        int v = G[i].to;
        if (v == fa[now]) continue;
        fa[v] = now;
        dfs1(v);
        siz[now] += siz[v];
        if (siz[v] > siz[son[now]]) son[now] = v;
    }
}
inline void dfs2(int now ,int t) {
    top[now] = t;
    if (!son[now]) return ;
    dfs2(son[now] ,t);
    for (int i = head[now]; i ; i = G[i].next) {
        int v = G[i].to;
        if (v == fa[now] || v == son[now]) continue;
        dfs2(v ,v);
    }
}
inline void dfs3(int now) {
    for (int i = head[now]; i ; i = G[i].next) {
        int v = G[i].to;
        if (v == fa[now]) continue;
        dfs3(v);
        a[now] += a[v];
    }
}
inline int LCA(int x ,int y) {
    while (top[x] != top[y]) {
        if (dep[top[x]] < dep[top[y]]) swap(x ,y);
        x = fa[top[x]];
    }
    return dep[x] < dep[y] ? x : y;
}
struct edge {
    int u ,v;
    edge (int u = 0 ,int v = 0) : u(u) ,v(v) {}
}e[N];
int n ,m;
signed main() {
    n = read();
    for (int i = 1; i <= n - 1; i++) {
        e[i].u = read(); e[i].v = read();
        add(e[i].u ,e[i].v);
    }
    dfs1(1); dfs2(1 ,1);
    m = read();
    while (m--) {
        int x = read() ,y = read();
        int lca = LCA(x ,y);
        a[x]++; a[y]++; a[lca] -= 2;
    }
    dfs3(1); //统计和
    for (int i = 1; i <= n - 1; i++) {
        int u = e[i].u ,v = e[i].v;
        printf("%d%c" ,dep[u] < dep[v] ? a[v] : a[u] ," \n"[i == n - 1]);
        //这条边的权值存在子节点上,子节点的深度更大,所以是取深度大的点的权值。
    }
    return 0;
}
posted @ 2021-04-20 13:22  recollector  阅读(70)  评论(0)    收藏  举报