树链剖分简单介绍

树剖想必各位大佬早已经烂熟于心
此篇博客大部分将 灌水 写写自己在学习树链剖分时对于各部分的理解

树链剖分是一种将树转化为链进行维护的数据结构
也可以说是将点按一定的顺序放在序列中,使得修改两个点之间的路径时将一段非连续序列转化为 多个连续区间,用线段树分别进行修改

如何进行排列?

一般来说,最有可能同时修改的点我们尽量将其放在一起
对于每个点定义其子节点中子树大小最大的为其 重儿子
其他子节点为 轻儿子 ,其连边分别为 重边轻边

显然,非叶子节点都有重边。将连续的重边链接成一条链,那就成为了重链,串起了一堆重儿子

我们就将这些重链上的点放入线段树中维护,只需保证其在一段连续的序列中并且按照深度递增即可
剩下的轻边轻儿子其实想放哪儿都行

可以证明两个点之间不会超过 logn log ⁡ n 条重链, logn log ⁡ n 条轻链

两个点通过重链,轻链不断向上跳,每次跳直接跳到当前链深度最小的点,直至相遇

emm,很简单的就不多讲了

HDU 3996

#include <bits/stdc++.h>
using namespace std;
typedef vector <int> ve;
const int N = 50010;
#define L (x << 1)
#define R (x << 1 | 1) 
#define mid ((l + r) >> 1)
ve e[N];
int n , m , p;
int a[N];
int F[N] , num[N];
int siz[N] , dep[N] , son[N] , fa[N];
int top[N] , pos[N] , fps[N] , sz;
int sum[N << 2] , tag[N << 2];

char str[10];
int read() {
    int ans = 0 , flag = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0') {if(ch == '-') flag = -1; ch = getchar();}
    while(ch >= '0' &&ch <= '9') {ans = ans * 10 + ch - '0'; ch = getchar();}
    return ans * flag;
}
int find(int x) {return x == F[x] ? F[x] : F[x] = find(F[x]);}
void merge(int u , int v) {
    u = find(u); v = find(v);
    if(u == v) return;
    if(num[u] > num[v]) swap(u , v);
    F[u] = v;
    num[v] += num[u]; num[u] = 0;
}
void dfs1(int now , int f , int deep) {
    fa[now] = f; son[now] = -1; siz[now] = 1; dep[now] = deep;
    for(ve :: iterator iter = e[now].begin() ; iter != e[now].end() ; iter ++) {
        if(*iter == f) continue;
        dfs1(*iter , now , deep + 1);
        siz[now] += siz[*iter];
        if(son[now] == -1 || siz[*iter] > siz[son[now]]) son[now] = *iter;
    }
}
void dfs2(int now , int _top) {
    top[now] = _top;
    pos[now] = ++ sz;
    fps[sz] = now;
    if(son[now] != -1) dfs2(son[now] , _top);
    for(ve :: iterator iter = e[now].begin() ; iter != e[now].end() ; iter ++) {
        if(*iter == fa[now] || *iter == son[now]) continue;
        dfs2(*iter , *iter);
    }
}
void build(int x , int l , int r) {
    if(l == r) sum[x] = a[fps[l]];
    else {build(L , l , mid); build(R , mid + 1 , r);}
}
int query(int x , int l , int r , int k) {
    if(l == r) return sum[x] + tag[x];
    if(k <= mid) return tag[x] + query(L , l , mid , k);
    else return tag[x] + query(R , mid + 1 , r , k); 
}
void change(int x , int l , int r , int ll , int rr , int k) {
    if(ll > rr) return;
    if(l == ll && r == rr) {tag[x] += k; return;}
    change(L , l , mid , ll , min(mid , rr) , k);
    change(R , mid + 1 , r , max(mid + 1 , ll) , rr , k);
}
void Change(int u , int v , int k) {
    int f1 = top[u] , f2 = top[v];
    while(f1 != f2) {
        if(dep[f1] < dep[f2]) {swap(f1 , f2); swap(u , v);} 
        change(1 , 1 , n , pos[f1] , pos[u] , k);
        u = fa[f1];
        f1 = top[u];
    }
    if(dep[u] > dep[v]) swap(u , v);
    change(1 , 1 , n , pos[u] , pos[v] , k);
}
void clear() {
    memset(sum , 0 , sizeof(sum));
    memset(tag , 0 , sizeof(tag));
    memset(siz , 0 , sizeof(siz));
    memset(dep , 0 , sizeof(dep));
    memset(son , 0 , sizeof(son));
    memset(fa , 0 , sizeof(fa));
    memset(top , 0 , sizeof(top));
    memset(pos , 0 , sizeof(pos));
    memset(fps , 0 , sizeof(fps));
    memset(e , 0 , sizeof(e));
    sz = 0;
}
int main() {
    while(~scanf("%d" , &n)) {
        clear();
        m = read(); p = read();
        for(int i = 1 ; i <= n ; ++ i) {a[i] = read(); F[i] = i; num[i] = 1;}
        for(int i = 1 ; i <= m ; ++ i) {
            int u = read() , v = read();
            e[u].push_back(v);
            e[v].push_back(u);
            merge(u , v);
        }
        for(int i = 1 ; i <= n ; ++ i)
            if(F[i] == i) {
                dfs1(i , 0 , 1);
                dfs2(i , i);
            }
        build(1 , 1 , n);
        for(int i = 1 ; i <= p ; ++ i) {
            scanf("%s" , str);
            if(str[0] == 'I') {
                int c1 = read() , c2 = read() , q = read();
                Change(c1 , c2 , q);
            }else if(str[0] == 'D') {
                int c1 = read() , c2 = read() , q = read();
                Change(c1 , c2 , - q);
            }else printf("%d\n" , query(1 , 1 , n , pos[read()]));
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值