CF276E Little Girl and Problem on Trees(线段树+树状数组)

Little Girl and Problem on Trees

题意:

给定一棵无边权的树,除了根节点以外的节点度数不超过 \(2\),有两种操作

  • 0 v x d)将距离 \(u\) 节点 \(d\) 距离之内的节点的值加上 \(x\)
  • 1 v)询问 \(u\) 节点的值

\(n\le 100000\)\(q\le 100000\)

思路:

通过两个操作的描述,可以知道要实现的是区间修改单点查询两个操作,不难想到要用线段树来实现.

//线段树
    struct Node {
        int tag, len;
        i64 sum;
    } tr[N << 2];

    void pull(int u) {
        tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
    }

    void build(int u, int l, int r) {
        tr[u].len = r - l + 1;
        if (l == r) return ;
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
    }

    void settag(int u, int x) {
        tr[u].sum += x * tr[u].len;
        tr[u].tag += x;
    }

    void push(int u) {
        if (!tr[u].tag) return ;
        settag(u << 1, tr[u].tag);
        settag(u << 1 | 1, tr[u].tag);
        tr[u].tag = 0;
    }

    void modify(int u, int l, int r, int ln, int rn, int x) {
        if (l >= ln && r <= rn) return void(settag(u, x));
        int mid = (l + r) >> 1;
        push(u);
        if (mid >= ln) modify(u << 1, l, mid, ln, rn, x);
        if (mid < rn) modify(u << 1 | 1, mid + 1, r, ln, rn, x);
        pull(u);
    }

    int query(int u, int l, int r, int pos) {
        if (l == r) return tr[u].sum;
        int mid = (l + r) >> 1;
        push(u);
        if (mid >= pos) return query(u << 1, l, mid, pos);
        else return query(u << 1 | 1, mid + 1, r, pos);
    }

但是这是在树上,所以很自然的就想到将整棵树剖成一个序列,用 \(dfs\) 序来表示这个树。

    struct node {
        int v, nxt;
    }e[N * 2];

    void connect(int a, int b) {
        e[++idx] = {b, h[a]}, h[a] = idx;
    }

    void dfs(int u, int fa) {
        dep[u] = dep[fa] + 1;
        top[u] = u;
        dfn[u] = ++cur;

        for (int i = h[u]; i; i = e[i].nxt) {
            if (e[i].v == fa) continue;
            dfs(e[i].v, u);
            if (dep[top[e[i].v]] > dep[top[u]]) 
                top[u] = top[e[i].v];
        }
    }

接下来要考虑操作一中距离节点 \(u\) 距离为 \(d\) 的所有节点都加上 \(x\) ,这个包括的范围是在 \(dfn[u] \pm d\) 所以修改的区间范围就出来了。但是不能够直接对 \([dfn_u - d, dfn_u + d]\) 这个区间进行区间修改。由于题目中说的除了根节点之外的所有的点度数不超过 \(2\) 所以可以知道所有的点都是在各自的链上,树大致长这个样, 其中 \(1\) 是根节点。
img

那么对于距离 \(d\) 就会出现 \(dfn_u + d\) 超过了这个链的长度,此时就要将右边界限制在这条链最深的节点的深度。再考虑左边界,如果 \(dep_u - d > 1\) 那么左边界就还是在这一条链上;但如果 \(dep_u - d < 0\) 就会出现离节点 \(u\) 距离为 \(d\) 的点出现在另外一条链上,这样的话线段树就很难操作了,因为其他所有的链都是要加上 \(x\) 的。那么可以考虑将需要加在所有链上的贡献归结到根节点上,将距离根节点 \(d - dep_u\) 的所有点都加上 \(x\) ,这个操作可以等同于单点加法,将所有点的深度作为区间,维护前缀和,基于这一点可以用树状数组来维护.

    void add(int x, int v) {
        for (int i = x; i <= n; i += i & -i) c[i] += v;
    }

    i64 ask(int x) {
        i64 ans = 0;
        for (int i = x; i; i -= i & -i) ans += c[i];
        return ans;
    }

    i64 range(int l, int r) {
        return ask(r) - ask(l);
    }

每一次查询的结果就是 \(tr_n - tr_{dep_u - 1}\)

    signed main() {
        std::cin.tie(nullptr)->sync_with_stdio(false);

        std::cin >> n >> q;
        for (int i = 1; i < n; i++) {
            int u, v;
            std::cin >> u >> v;
            connect(u, v), connect(v, u);
        }

        dfs(1, 0);
        build(1, 1, n);

        for (int i = 0; i < q; i++) {
            int op, v;
            std::cin >> op >> v;
            if (op == 0) {
                int x, d; std::cin >> x >> d;
                if (v == 1) {
                    add(d + 1, x);
                    continue;
                }
                
                int l = 0, r = dfn[v] + std::min(d, dep[top[v]] - dep[v]);

                if (dep[v] - 1 > d) {
                    l = dfn[v] - d;
                } else {
                    l = dfn[v] - (dep[v] - 2);
                    int p = d - dep[v] + 1;
                    modify(1, 1, n, l, std::min(p + l - 1, dfn[top[v]]), -x);
                    add(p + 1, x);
                }

                modify(1, 1, n, l, r, x);
            } else {
                i64 ans = query(1, 1, n, dfn[v]);
                ans += range(dep[v] - 1, n);
                std::cout << ans << "\n";       
            }
        }

        return 0 ^ 0;
    }
posted @ 2022-10-10 15:50  浅渊  阅读(28)  评论(0)    收藏  举报