• 【NOI复习】树链剖分


    简介

      树链剖分通常用来解决一类维护静态树上路径信息的问题, 例如:
    给定一棵点带权树, 接下来每次操作会修改某条路径上所有点的权值(修改为同一个值或是同加上一个值等) , 以及询问某条路径上所有点的权值和。
    当这棵树是一条链时, 这个问题实际上就是一个序列上区间修改、 区间询问的问题, 可以用之前介绍的几个数据结构解决。
    对于其他情况, 由于树的形态是不变的, 因此树链剖分的策略是将这些点按某种方式组织起来, 剖分成为若干条链, 每条链就相当于一个序列, 则操作路径可以拆分为剖分好的某几条链, 也就是若干个完整序列或是某个序列上的一段区间, 此时再利用线段树等处理序列上区间操作问题的数据结构来解决。
    树链剖分的核心就是如何恰当的剖分树为若干条链。 当链的划分方式确定后, 我们只要将它们看做是一个个序列, 将所有序列按顺序拼接起来后, 每条链就成为了一段区间, 而序列上的区间问题是我们所熟悉和擅长解决的。

    方法

    轻重链剖分

    我们将树中的边分成两种: 轻边, 重边。 如下图中加粗的边是重边, 其余是轻边。

    我们可以以任意点为根, 然后记 size(u) 为以 u 为根的子树的结点个数, 令 v u 所有儿子中 size 值最大的一个儿子, 则(u,v) 为重边, v 称为u 的重儿子。 u 到其余儿子的边为
    轻边。

     

    树链剖分求LCA

      传送门

     

    例题

    【浙江省选2008】树的统计

    题目背景

    ZJOI2008 DAY1 T4

    题目描述

    一棵树上有 n 个节点,编号分别为 1 到 n ,每个节点都有一个权值 w 。
    我们将以下面的形式来要求你对这棵树完成一些操作:
    I.CHANGE u t :把结点 u 的权值改为 t ;
    II.QMAX u v :询问从点 u 到点 v 的路径上的节点的最大权值;
    III.QSUM u v :询问从点 u 到点 v 的路径上的节点的权值和。

    注意:从点 u 到点 v 的路径上的节点包括 u 和 v 本身。

    输入格式

    输入第一行为一个整数 n ,表示节点的个数。
    接下来 n–1 行,每行 2 个整数 a 和 b ,表示节点 a 和节点 b 之间有一条边相连。
    接下来 n 行,每行一个整数,第 i 行的整数 wi 表示节点 i 的权值。
    接下来 1 行,为一个整数 q ,表示操作的总数。
    接下来 q 行,每行一个操作,以“CHANGE u t”或者“QMAX u v”或者“QSUM u v”的形式给出。

    输出格式

    对于每个“QMAX”或者“QSUM”的操作,每行输出一个整数表示要求输出的结果。

    样例数据 1

    输入


    1 2 
    2 3 
    4 1 
    4 2 1 3 
    12 
    QMAX 3 4 
    QMAX 3 3 
    QMAX 3 2 
    QMAX 2 3 
    QSUM 3 4 
    QSUM 2 1 
    CHANGE 1 5 
    QMAX 3 4 
    CHANGE 3 6 
    QMAX 3 4 
    QMAX 2 4 
    QSUM 3 4

    输出





    10 




    16

    备注

    【数据范围】

    对于 100% 的数据,保证1<=n<=30000;0<=q<=200000;中途操作中保证每个节点的权值 w 在 -30000 到 30000 之间。

    【题目分析】

    模板题

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<string>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    
    const int N = 30050;
    const int oo = 0x3f3f3f3f;
    
    int dep[N], sze[N], top[N], son[N], pos[N], idx[N], val[N], fa[N];
    int ecnt, adj[N], go[N << 1], nxt[N << 1], tot;
    int sum[N * 4], maxx[N * 4];
    int n, q;
    
    inline int Re(){
        int i = 0, f = 1; char ch = getchar();
        for(; (ch < '0' || ch > '9') && ch != '-'; ch = getchar());
        if(ch == '-') f = -1, ch = getchar();
        for(; ch >= '0' && ch <= '9'; ch = getchar())
            i = (i << 3) + (i << 1) + (ch - '0');
        return i * f;
    }
    
    inline void Wr(int x){
        if(x < 0) putchar('-'), x = -x;
        if(x > 9) Wr(x / 10);
        putchar(x % 10 + '0');
    }
    
    inline void addEdge(const int &u, const int &v){
        nxt[++ecnt] = adj[u], adj[u] = ecnt, go[ecnt] = v;
        nxt[++ecnt] = adj[v], adj[v] = ecnt, go[ecnt] = u;
    }
    
    inline void dfs1(const int &u, const int &f){
        dep[u] = dep[f] + 1;
        fa[u] = f;
        sze[u] = 1;
        for(int e = adj[u]; e; e = nxt[e]){
            int v = go[e];
            if(v == f) continue;
            dfs1(v, u);
            sze[u] += sze[v];
            if(sze[v] > sze[son[u]]) son[u] = v;
        }
    }
    
    inline void dfs2(const int &u, const int &f){
        if(son[u]){   //先查重儿子, 保证重链连续 
            top[son[u]] = top[u];
            idx[pos[son[u]] = ++tot] = son[u];   
            dfs2(son[u], u);
        }
        for(int e = adj[u]; e; e = nxt[e]){
            int v = go[e];
            if(v == f || v == son[u]) continue;
            top[v] = v;
            idx[pos[v] = ++tot] = v;
            dfs2(v, u);
        }
    }
    
    inline int chkMax(const int &x, const int &y){
        if(x > y) return x;
        return y;
    }
    
    inline void build(int k, int l, int r){ 
        if(l == r){
            sum[k] = maxx[k] = val[idx[l]];
            return;
        }
        int mid = l + r >> 1, lc = k << 1, rc = k << 1 | 1;
        build(lc, l, mid);
        build(rc, mid + 1, r);
        sum[k] = sum[lc] + sum[rc];
        maxx[k] = chkMax(maxx[lc], maxx[rc]);
    }
    
    inline int PathSum(int k, int l, int r, int x, int y){
        if(x <= l && r <= y) return sum[k];
        int mid = l + r >> 1, lc = k << 1, rc = k << 1 | 1;
        int ret = 0;
        if(x <= mid) ret += PathSum(lc, l, mid, x, y);
        if(y > mid) ret += PathSum(rc, mid + 1, r, x, y);
        return ret;
    }
    
    inline int PathMax(int k, int l, int r, int x, int y){
        if(x <= l && r <= y) return maxx[k];
        int mid = l + r >> 1, lc = k << 1, rc = k << 1 | 1;
        int ret = -oo;
        if(x <= mid) ret = chkMax(ret, PathMax(lc, l, mid, x, y));
        if(y > mid) ret = chkMax(ret, PathMax(rc, mid + 1, r, x, y));
        return ret;
    }
    
    inline void PrintSum(int u, int v){
        int ret = 0;
        while(top[u] != top[v]){
            if(dep[top[u]] < dep[top[v]]) swap(u, v);
            ret += PathSum(1, 1, n, pos[top[u]], pos[u]);
            u = fa[top[u]];
        }
        if(dep[u] > dep[v]) swap(u, v);
        ret += PathSum(1, 1, n, pos[u], pos[v]);
        Wr(ret), putchar('
    ');
    }
    
    inline void PrintMax(int u, int v){
        int ret = -oo;
        while(top[u] != top[v]){
            if(dep[top[u]] < dep[top[v]]) swap(u, v);
            ret = chkMax(ret, PathMax(1, 1, n, pos[top[u]], pos[u]));
            u = fa[top[u]];
        }
        if(dep[u] > dep[v]) swap(u, v);
        ret = chkMax(ret, PathMax(1, 1, n, pos[u], pos[v]));
        Wr(ret), putchar('
    ');
    }
    
    inline void modify(int k, int l, int r, int pos, int v){
        if(l == r){
            sum[k] = v;
            maxx[k] = v;
            return;
        }
        int mid = l + r >> 1, lc = k << 1, rc = k << 1 | 1;
        if(pos <= mid) modify(lc, l, mid, pos, v);
        else modify(rc, mid + 1, r, pos, v);
        sum[k] = sum[lc] + sum[rc];
        maxx[k] = chkMax(maxx[lc], maxx[rc]);
    }
    
    inline void print(int k){
        if(k == 0) return;
        print(k<<1);print(k<<1|1);
        cout<<sum[k]<<" "<<maxx[k]<<endl;
    }
    
    int main(){
    //    freopen("h.in", "r", stdin);
        n = Re();
        for(int i = 1; i < n; i++){
            int a = Re(), b = Re();
            addEdge(a, b);
        }
        for(int i = 1; i <= n; i++) val[i] = Re();
        
        dep[0] = -1, top[1] = 1, idx[1] = 1, pos[1] = 1, tot = 1;
        dfs1(1, 0);
        dfs2(1, 0);
        build(1, 1, n);
    //    print(1);
        
        q = Re();
        for(int i = 1; i <= q; i++){
            char opt[20]; int u, v, t;
            scanf("%s", opt + 1);
            if(opt[2] == 'H'){   //change
                u = Re(), t = Re();
                modify(1, 1, n, pos[u], t);
            }
            else if(opt[2] == 'M'){   //qmax
                u = Re(), v = Re();
                PrintMax(u, v);
            }
            else{  //qsum
                u = Re(), v = Re();
                PrintSum(u, v);
            }
        }
    }
    View Code

     

     【bzoj2243】【山东省选2011】染色

    Description

    给定一棵有n个节点的无根树和m个操作,操作有2类:

    1、将节点a到节点b路径上所有点都染成颜色c;

    2、询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”3段组成:“11”、“222”和“1”

    请你写一个程序依次完成这m个操作。

    Input

    第一行包含2个整数n和m,分别表示节点数和操作数;

    第二行包含n个正整数表示n个节点的初始颜色

    下面 行每行包含两个整数x和y,表示xy之间有一条无向边。

    下面 行每行描述一个操作:

    “C a b c”表示这是一个染色操作,把节点a到节点b路径上所有点(包括a和b)都染成颜色c;

    “Q a b”表示这是一个询问操作,询问节点a到节点b(包括a和b)路径上的颜色段数量。

    Output

    对于每个询问操作,输出一行答案。

    Sample Input

    6 5
    2 2 1 2 1 1
    1 2
    1 3
    2 4
    2 5
    2 6
    Q 3 5
    C 2 1 1
    Q 3 5
    C 5 1 2
    Q 3 5

    Sample Output

    3
    1
    2

    HINT

    数N<=10^5,操作数M<=10^5,所有的颜色C为整数且在[0, 10^9]之间。

    【题目分析】

     

     树链剖分,维护节点的颜色段数, 修改标记, 左端、右端颜色, 注意用左右子树更新根节点时颜色相同要-1, 数组线段树不好维护可以写成结构体!!

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<string>
    #include<algorithm>
    #include<cmath>
    #include<vector>
    using namespace std;
    
    const int N = 3e5;
    
    int n, m;
    int sze[N], dep[N], val[N], idx[N], pos[N], fa[N], top[N], son[N], tot;
    int ecnt, adj[N], go[N << 1], nxt[N << 1];
    
    inline int Re(){
        int i = 0, f = 1; char ch = getchar();
        for(; (ch < '0' || ch > '9') && ch != '-'; ch = getchar());
        if(ch == '-') f = -1, ch = getchar();
        for(; ch >= '0' && ch <= '9'; ch = getchar())
            i = (i << 3) + (i << 1) + (ch - '0');
        return i * f;
    }
    
    inline void Wr(int x){
        if(x < 0) putchar('-'), x = -x;
        if(x > 9) Wr(x / 10);
        putchar(x % 10 + '0');
    }
    
    inline void addEdge(const int &u, const int &v){
        nxt[++ecnt] = adj[u], adj[u] = ecnt, go[ecnt] = v;
        nxt[++ecnt] = adj[v], adj[v] = ecnt, go[ecnt] = u;
    }
    
    inline void dfs1(const int &u, const int &f){
        dep[u] = dep[f] + 1;
        sze[u] = 1;
        fa[u] = f;
        for(int e = adj[u]; e; e = nxt[e]){
            int v = go[e];
            if(v == f) continue;
            dfs1(v, u);
            sze[u] += sze[v];
            if(sze[v] > sze[son[u]]) son[u] = v;
        }
    }
    
    inline void dfs2(const int &u, const int &f){
        if(son[u]){
            top[son[u]] = top[u];
            idx[pos[son[u]] = ++tot] = son[u];
            dfs2(son[u], u);
        }
        for(int e = adj[u]; e; e = nxt[e]){
            int v = go[e];
            if(v == f || v == son[u]) continue;
            top[v] = v;
            idx[pos[v] = ++tot] = v;
            dfs2(v, u);
        }
    }
    
    struct node{
        int cnt, lcol, rcol, tag;
        node():cnt(0), lcol(-1), rcol(-1), tag(-1){}
    };
    
    namespace SegTree{
        node tr[N * 4];
        inline void upt(int k){
            int lc = k << 1, rc = k << 1 | 1;
            tr[k].lcol = tr[lc].lcol;
            tr[k].rcol = tr[rc].rcol;
            tr[k].cnt = tr[lc].cnt + tr[rc].cnt - (tr[lc].rcol == tr[rc].lcol);
        }
        inline void cover(int k, int v){
            tr[k].lcol = tr[k].rcol = v;
            tr[k].cnt = 1;
            tr[k].tag = v;
        }
        inline void pushDown(int k){
            int lc = k << 1, rc = k << 1 | 1;
            if(tr[k].tag != -1){
                cover(lc, tr[k].tag);
                cover(rc, tr[k].tag);
                tr[k].cnt = 1, tr[k].lcol = tr[k].rcol = tr[k].tag;
                tr[k].tag = -1;
            }
        }
        
        inline void build(int k, int l, int r){
            if(l == r){
                tr[k].cnt = 1;
                tr[k].tag = -1;
                tr[k].lcol = tr[k].rcol = val[idx[l]];
                return;
            }
            int mid = l + r >> 1, lc = k << 1, rc = k << 1 | 1;
            build(lc, l, mid);
            build(rc, mid + 1, r);
            upt(k);
        }
        
        inline void modify(int k, int l, int r, int x, int y, int v){
            if(x <= l && r <= y){
                cover(k, v);
                return;
            }
            pushDown(k);
            int mid = l + r >> 1, lc = k << 1, rc = k << 1 | 1;
            if(x <= mid) modify(lc, l, mid, x, y, v);
            if(y > mid) modify(rc, mid + 1, r, x, y, v);
            upt(k);
        }
        
        inline node query(int k, int l, int r, int x, int y){
            if(l == x && r == y) return tr[k];
            pushDown(k);
            int mid = l + r >> 1, lc = k << 1, rc = k << 1 | 1;
            if(y <= mid) return query(lc, l, mid, x, y);
            else if(x > mid) return query(rc, mid + 1, r, x, y);
            else {
                node ret, ret1, ret2;
                ret1 = query(lc, l, mid, x, mid);
                ret2 = query(rc, mid + 1, r, mid + 1, y);
                ret.cnt = ret1.cnt + ret2.cnt - (ret1.rcol == ret2.lcol);
                ret.lcol = ret1.lcol, ret.rcol = ret2.rcol;
                return ret;
            }
    //        cout<<ret1.lcol<<" "<<ret1.lcol<<" "<<ret2.lcol<<" "<<ret2.rcol<<endl;    
            
        } 
    }using namespace SegTree;
    inline void PrintCnt(int a, int b){
        int ans = 0, acol = -1, bcol = -1;
        while(top[a] != top[b]){
            if(dep[top[a]] < dep[top[b]]) swap(a, b), swap(acol, bcol);
            node ret = query(1, 1, n, pos[top[a]], pos[a]);
            ans += ret.cnt;
            if(ret.rcol == acol) ans--;
            a = fa[top[a]], acol = ret.lcol;
        }
        if(dep[a] > dep[b]) swap(a, b), swap(acol, bcol);
        node ret = query(1, 1, n, pos[a], pos[b]);
        ans += ret.cnt - (ret.lcol == acol) - (ret.rcol == bcol);
        Wr(ans);
    }
    
    inline void PathModify(int a, int b, int v){
        while(top[a] != top[b]){
            if(dep[top[a]] < dep[top[b]]) swap(a, b);
            modify(1, 1, n, pos[top[a]], pos[a], v);
            a = fa[top[a]];
        }
        if(dep[a] > dep[b]) swap(a, b);
        modify(1, 1, n, pos[a], pos[b], v);
    }
    
    int main(){
        freopen("h.in", "r", stdin);
        n = Re(), m = Re();
        for(int i = 1; i <= n; i++) val[i] = Re();
        for(int i = 1; i < n; i++){
            int a = Re(), b = Re();
            addEdge(a, b);
        }
        dep[0] = -1, top[1] = pos[1] = idx[1] = tot = 1;
        dfs1(1, 0);
        dfs2(1, 0);
        build(1, 1, n); 
        for(int i = 1; i <= m; i++){
            char opt; opt = getchar();
            while(opt != 'Q' && opt != 'C') opt = getchar();
            int a, b, c;
            if(opt == 'C'){
                a = Re(), b = Re(), c = Re();
                PathModify(a, b, c);
            }
            else if(opt == 'Q'){
                a = Re(), b = Re();
                PrintCnt(a, b), putchar('
    ');
            }
        }
        return 0;
    }
    View Code

     

  • 相关阅读:
    To do list
    2020 上半学期比赛记录
    板子
    Project Euler 1~10 野蛮题解
    卡常火车头
    防止unordered_map 被卡方法
    2019 香港区域赛 BDEG 题解
    2019徐州区域赛 ACEFM 题解 & pollard-rho & miller-rabin & 求出每个子树的重心 板子
    BST-splay板子
    ZJOI2017(2) 游记
  • 原文地址:https://www.cnblogs.com/CzYoL/p/7205216.html
Copyright © 2020-2023  润新知