树链剖分专题

入门的话,这篇还是写的不错的:
http://blog.sina.com.cn/s/blog_6974c8b20100zc61.html

一个入门小专题:https://vjudge.net/contest/158100#overview

1.SPOJ QTREE 树链剖分+线段树
题意:给两个操作,一个是把第i条边权值修改成ti,另一个是查询a到b之间最大的边权值

基于边权,修改单条边权,查询路径边权最大值
代码:

#include <set>
#include <map>
#include <stack>
#include <queue>
#include <string>
#include <vector>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <string.h>
#include <algorithm>
using  namespace  std;

#define ff first
#define ss second
#define pb push_back
#define ll long long
#define mod 1000000007
#define ull unsigned long long
#define mst(ss,b) memset(ss,b,sizeof(ss));
#define dbg(x) cout << #x << "= " << x << endl;
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
const int inf = 0x3f3f3f3f;
const int N = 5e4+5;

int n, m, q;

struct Edge{
    int to, next;
}edge[N<<1];

int head[N], tol;
int top[N];//top[v]表示v所在的重链的顶端节点
int fa[N];//父亲节点
int dep[N];//深度
int sz[N];//sz[v]表示以v为根的子树的节点数
int p[N];//p[v]表示v与其父亲节点的连边在线段树中的位置
int fp[N];//和p数组相反  已知v在线段树中位置,其父亲节点是u
int son[N];//重儿子
int pos;

int a[N];

void init(){
    tol = 0;
    memset(head, -1, sizeof(head));
    pos = 0;  
    memset(son, -1, sizeof(son));
}

inline void addedge(int u, int  v){
    edge[tol].to = v;edge[tol].next = head[u]; head[u] = tol++;
}

void dfs1(int u, int pre, int d){//第一遍dfs求出fa,deep,sz,son
    dep[u] = d;
    fa[u] = pre;
    sz[u] = 1;
    for(int i=head[u]; i!=-1; i=edge[i].next){
        int v = edge[i].to;
        if(v != pre){
            dfs1(v, u, d+1);
            sz[u] += sz[v];
            if(son[u] == -1 || sz[v] > sz[son[u]])
                son[u] = v;
        }
    }
}

void getpos(int u, int sp){ //第二遍dfs求出top和p
    top[u] = sp;
    p[u] = ++pos;  //树状数组编号从1开始
    fp[p[u]] = u;
    if(son[u] == -1)return ;
    getpos(son[u], sp);
    for(int i=head[u]; i!=-1; i=edge[i].next){
        int v = edge[i].to;
        if(v != son[u] && v != fa[u])
            getpos(v, v); //轻儿子
    }
}

struct BIT{
    int n;
    int c[N];

    void init(int _n){
        n = _n;
        for(int i=1; i<=n; i++)c[i] = 0;
        mst(c, 0);
    }

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

    int query(int p){
        int ret = 0;
        for(int i=p; i>0; i-=i&-i)ret += c[i];
        return ret;
    }

}bit;

void solve(int u, int v, int val){
    int f1 = top[u], f2 = top[v];
    int tmp = 0;
    while(f1 != f2){
        if(dep[f1] < dep[f2]){
            swap(f1, f2);
            swap(u, v);
        }
        bit.update(p[f1], val);
        bit.update(p[u]+1, -val);
        u = fa[f1];  f1 = top[u];
    }
    //if(u == v)return ;  加了就wa了,因为树状数组区间修改不能少了下面的操作
    if(dep[u] > dep[v])swap(u, v);
    bit.update(p[u], val);
    bit.update(p[v]+1, -val);
}

int  main(){
    while(~scanf("%d%d%d", &n, &m, &q)){
        init();
        for(int i=1; i<=n; i++)scanf("%d", &a[i]);
        for(int i=1; i<=m; i++){
            int  u, v;
            scanf("%d%d", &u, &v);
            addedge(u, v);
            addedge(v, u);
        }
        dfs1(1, -1, 0);
        getpos(1, 1);
        bit.init(pos);
        for(int i=1; i<=n; i++){
            bit.update(p[i], a[i]);
            bit.update(p[i]+1, -a[i]);
        }
        while(q--){
            char op[10];
            scanf("%s", op);
            if(op[0] == 'Q'){
                int C;
                scanf("%d", &C);
                printf("%d\n", bit.query(p[C]));
            }
            else{
                int C1, C2, K;
                scanf("%d%d%d", &C1, &C2, &K);
                if(op[0] == 'D')K = -K;
                solve(C1, C2, K);
            }
        }
    }
    return 0;
}

2.HDU 3966 Aragorn’s Story (树链剖分+树状数组)
题意:
给一棵树,并给定各个点权的值,然后有3种操作:
I C1 C2 K: 把C1与C2的路径上的所有点权值加上K
D C1 C2 K:把C1与C2的路径上的所有点权值减去K
Q C:查询节点编号为C的权值
基于点权,查询单点值,修改路径的上的点权

树状数组区间修改,单点查询可以参考这篇文章:
http://blog.csdn.net/kenden23/article/details/29854527

代码:

#include <set>
#include <map>
#include <stack>
#include <queue>
#include <string>
#include <vector>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <string.h>
#include <algorithm>
using  namespace  std;

#define ff first
#define ss second
#define pb push_back
#define ll long long
#define mod 1000000007
#define ull unsigned long long
#define mst(ss,b) memset(ss,b,sizeof(ss));
#define dbg(x) cout << #x << "= " << x << endl;
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
const int inf = 0x3f3f3f3f;
const int N = 5e4+5;

int n, m, q;

struct Edge{
    int to, next;
}edge[N<<1];

int head[N], tol;
int top[N];//top[v]表示v所在的重链的顶端节点
int fa[N];//父亲节点
int dep[N];//深度
int sz[N];//sz[v]表示以v为根的子树的节点数
int p[N];//p[v]表示v与其父亲节点的连边在线段树中的位置
int fp[N];//和p数组相反  已知v在线段树中位置,其父亲节点是u
int son[N];//重儿子
int pos;

int a[N];

void init(){
    tol = 0;
    memset(head, -1, sizeof(head));
    pos = 0;  
    memset(son, -1, sizeof(son));
}

inline void addedge(int u, int  v){
    edge[tol].to = v;edge[tol].next = head[u]; head[u] = tol++;
}

void dfs1(int u, int pre, int d){//第一遍dfs求出fa,deep,sz,son
    dep[u] = d;
    fa[u] = pre;
    sz[u] = 1;
    for(int i=head[u]; i!=-1; i=edge[i].next){
        int v = edge[i].to;
        if(v != pre){
            dfs1(v, u, d+1);
            sz[u] += sz[v];
            if(son[u] == -1 || sz[v] > sz[son[u]])
                son[u] = v;
        }
    }
}

void getpos(int u, int sp){ //第二遍dfs求出top和p
    top[u] = sp;
    p[u] = ++pos;  //树状数组编号从1开始
    fp[p[u]] = u;
    if(son[u] == -1)return ;
    getpos(son[u], sp);
    for(int i=head[u]; i!=-1; i=edge[i].next){
        int v = edge[i].to;
        if(v != son[u] && v != fa[u])
            getpos(v, v); //轻儿子
    }
}

struct BIT{
    int n;
    int c[N];

    void init(int _n){
        n = _n;
        for(int i=1; i<=n; i++)c[i] = 0;
        mst(c, 0);
    }

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

    int query(int p){
        int ret = 0;
        for(int i=p; i>0; i-=i&-i)ret += c[i];
        return ret;
    }

}bit;

void solve(int u, int v, int val){
    int f1 = top[u], f2 = top[v];
    int tmp = 0;
    while(f1 != f2){
        if(dep[f1] < dep[f2]){
            swap(f1, f2);
            swap(u, v);
        }
        bit.update(p[f1], val);
        bit.update(p[u]+1, -val);
        u = fa[f1];  f1 = top[u];
    }
    //if(u == v)return ;  加了就wa了,因为树状数组区间修改不能少了下面的操作
    if(dep[u] > dep[v])swap(u, v);
    bit.update(p[u], val);
    bit.update(p[v]+1, -val);
}

int  main(){
    while(~scanf("%d%d%d", &n, &m, &q)){
        init();
        for(int i=1; i<=n; i++)scanf("%d", &a[i]);
        for(int i=1; i<=m; i++){
            int  u, v;
            scanf("%d%d", &u, &v);
            addedge(u, v);
            addedge(v, u);
        }
        dfs1(1, -1, 0);
        getpos(1, 1);
        bit.init(pos);
        for(int i=1; i<=n; i++){
            bit.update(p[i], a[i]);
            bit.update(p[i]+1, -a[i]);
        }
        while(q--){
            char op[10];
            scanf("%s", op);
            if(op[0] == 'Q'){
                int C;
                scanf("%d", &C);
                printf("%d\n", bit.query(p[C]));
            }
            else{
                int C1, C2, K;
                scanf("%d%d%d", &C1, &C2, &K);
                if(op[0] == 'D')K = -K;
                solve(C1, C2, K);
            }
        }
    }
    return 0;
}

3.POJ3237 Tree
题意:
一棵树有n个节点,n-1条边,每条边有一个权值,三种操作
CHANGE i v:将第i条边的权值改成v
NEGATE a b:将a到b路径上所有边的权值取反(x->-x)
QUERY a b:查询a到b路径上所有边权值的最大值
思路:
树链剖分,然后建线段树,由于此题有取反操作,所以树中元素不仅要存区间最大值,还要存区间最小值,同时需要一个lazy表示该区间是否被取反,之后就是线段树单点修改、区间更新以及区间查询问题了

代码:

#include <set>
#include <map>
#include <queue>
#include <vector>
#include <math.h>
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
using  namespace  std;

#define ff first
#define ss second
#define pb push_back
#define ll long long
#define mod 1000000007
#define ull unsigned long long
#define min3(a, b, c) min(a, min(b, c))
#define max3(a, b, c) max(a, max(b, c))
#define mst(ss,b) memset(ss,b,sizeof(ss));
#define dbg(x) cout << #x << "= " << x << endl;
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
typedef pair <int, int> pii;
const int inf = 0x3f3f3f3f;
const ll INF = (1LL<<63)-1;
const int N = 1e4+5;

struct Edge{
    int to, next;
}edge[N<<1];

int head[N], tol;
int top[N];//top[v]表示v所在的重链的顶端节点
int fa[N];//父亲节点
int dep[N];//深度
int sz[N];//sz[v]表示以v为根的子树的节点数
int p[N];//p[v]表示v与其父亲节点的连边在线段树中的位置
int fp[N];//和p数组相反  已知v在线段树中位置,其父亲节点是u
int son[N];//重儿子
int pos; //pos为线段树或者树状数组的大小

void init(){
    tol = 0;
    memset(head, -1, sizeof(head));
    pos = 0;
    memset(son, -1, sizeof(son));
}

void addedge(int u, int  v){
    edge[tol].to = v;edge[tol].next = head[u]; head[u] = tol++;
}

void dfs1(int u, int pre, int d){//第一遍dfs求出fa,deep,sz,son
    dep[u] = d;
    fa[u] = pre;
    sz[u] = 1;
    for(int i=head[u]; i!=-1; i=edge[i].next){
        int v = edge[i].to;
        if(v != pre){
            dfs1(v, u, d+1);
            sz[u] += sz[v];
            if(son[u] == -1 || sz[v] > sz[son[u]])
                son[u] = v;
        }
    }
}

void getpos(int u, int sp){ //第二遍dfs求出top和p
    top[u] = sp;
    p[u] = ++pos;
    fp[p[u]] = u;
    if(son[u] == -1)return ;
    getpos(son[u], sp);
    for(int i=head[u]; i!=-1; i=edge[i].next){
        int v = edge[i].to;
        if(v != son[u] && v != fa[u])
            getpos(v, v); //轻儿子
    }
}

int Max[N<<2];
int Min[N<<2];
int lazy[N<<2];

void PushUp(int rt){
    Max[rt] = max(Max[rt<<1], Max[rt<<1|1]);
    Min[rt] = min(Min[rt<<1], Min[rt<<1|1]);
}

void change(int rt){
    Max[rt] = -Max[rt];
    Min[rt] = -Min[rt];
    swap(Max[rt], Min[rt]);
}

void PushDown(int rt){
    if(lazy[rt]){
        lazy[rt] ^= 1;
        lazy[rt<<1] ^= 1;
        lazy[rt<<1|1] ^= 1;
        change(rt<<1);
        change(rt<<1|1);
    }
}

void build(int l, int r, int rt){
    if(l == r){
//        Max[rt] = 0;
//        Min[rt] = 0;
//        lazy[rt] = 0;
        return ;
    }
    int m = (l+r)>>1;
    build(lson);
    build(rson);
    PushUp(rt);
}

void update(int p, int re, int l, int r, int rt){
    if(l == r){
        Max[rt] = Min[rt] = re;
        return ;
    }
    PushDown(rt);
    int m = (l+r)>>1;
    if(p <= m)update(p, re, lson);
    else update(p, re, rson);
    PushUp(rt);
}

void ne_update(int L, int R, int l, int r, int rt){
    if(L <= l && r <= R){
        lazy[rt] ^= 1;
        change(rt);
        return ;
    }
    PushDown(rt);
    int m = (l+r)>>1;
    if(L <= m)ne_update(L, R, lson);
    if(R > m)ne_update(L, R, rson);
    PushUp(rt);
}

int query(int L, int R, int l, int r, int rt){
    if(L <= l && r <= R){
        return Max[rt];
    }
    PushDown(rt);
    int m = (l+r)>>1;
    int ret = -inf;   初始化
    if(L <= m)ret = max(ret, query(L, R, lson));
    if(R > m)ret = max(ret, query(L, R, rson));
    return ret;
}

int getmax(int u, int v){
    int f1 = top[u], f2 = top[v];
    int tmp = -inf;  初始化
    while(f1 != f2){
        if(dep[f1] < dep[f2]){
            swap(f1, f2);
            swap(u, v);
        }
        tmp = max(tmp, query(p[f1], p[u], 1, pos, 1));
        u = fa[f1]; f1 = top[u];
    }
    if(u == v)return tmp; 
    if(dep[u] >dep[v])swap(u, v);
    return max(tmp, query(p[son[u]], p[v], 1, pos, 1));
}

void Negate(int u, int v){
    int f1 = top[u], f2 = top[v];
    while(f1 != f2){
        if(dep[f1] < dep[f2]){
            swap(f1, f2);
            swap(u, v);
        }
        ne_update(p[f1], p[u], 1, pos, 1);
        u = fa[f1]; f1 = top[u];
    }
    if(u == v)return ; 
    if(dep[u] > dep[v])swap(u, v);
    return ne_update(p[son[u]], p[v], 1, pos, 1);
}

int e[N][3];

int  main(){
    int T;
    scanf("%d", &T);
    while(T--){
        init();
        int n;
        scanf("%d", &n);
        for(int i=1; i<n; i++){
            for(int j=0; j<3; j++){
                scanf("%d", &e[i][j]);
            }
            addedge(e[i][0], e[i][1]);
            addedge(e[i][1], e[i][0]);
        }
        dfs1(1, -1, 0);
        getpos(1, 1);

        build(1, pos, 1);
        for(int i=1; i<n; i++){
            if(dep[e[i][0]] > dep[e[i][1]])swap(e[i][0], e[i][1]);
            update(p[e[i][1]], e[i][2], 1, pos, 1);
        }
        char op[10];
        while(~scanf("%s", op)){
            if(op[0] == 'D')break;
            int u, v;
            scanf("%d%d", &u, &v);
            if(op[0] == 'C')update(p[e[u][1]], v, 1, pos, 1);
            else if(op[0] == 'Q')printf("%d\n", getmax(u, v));
            else Negate(u, v);
        }

    }

    return 0;
}

4.2017川大邀请赛A题 scu4549 Greed King
题意:一颗树支持两种操作:
1.给点a到b之间所有的边的边权增加c
2.查询点a到b之间的边权和

思路:简单的树链剖分,用线段树维护区间增和区间查询操作

代码:

#include <set>
#include <map>
#include <queue>
#include <vector>
#include <math.h>
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
using  namespace  std;

#define ff first
#define ss second
#define pb push_back
#define ll long long
#define mod 1000000007
#define ull unsigned long long
#define min3(a, b, c) min(a, min(b, c))
#define max3(a, b, c) max(a, max(b, c))
#define mst(ss,b) memset(ss,b,sizeof(ss));
#define dbg(x) cout << #x << "= " << x << endl;
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
typedef pair <int, int> pii;
const int inf = 0x3f3f3f3f;
const ll INF = (1LL<<63)-1;
const int N = 1e5+5;

struct node{
    int to, next;
}edge[N<<1];

int head[N], tol;
int top[N];//top[v]表示v所在的重链的顶端节点
int fa[N];//父亲节点
int dep[N];//深度
int sz[N];//sz[v]表示以v为根的子树的节点数
int p[N];//p[v]表示v与其父亲节点的连边在线段树中的位置
int fp[N];//和p数组相反  已知v在线段树中位置,其父亲节点是u
int son[N];//重儿子
int pos; //pos为线段树或者树状数组的大小

void init(){
    tol = 0;
    memset(head, -1, sizeof(head));
    pos = 0;
    memset(son, -1, sizeof(son));
}

void addedge(int u, int  v){
    edge[tol].to = v;edge[tol].next = head[u]; head[u] = tol++;
}

void dfs1(int u, int pre, int d){//第一遍dfs求出fa,deep,sz,son
    dep[u] = d;
    fa[u] = pre;
    sz[u] = 1;
    for(int i=head[u]; i!=-1; i=edge[i].next){
        int v = edge[i].to;
        if(v != pre){
            dfs1(v, u, d+1);
            sz[u] += sz[v];
            if(son[u] == -1 || sz[v] > sz[son[u]])
                son[u] = v;
        }
    }
}

void getpos(int u, int sp){ //第二遍dfs求出top和p
    top[u] = sp;
    p[u] = ++pos;
    fp[p[u]] = u;
    if(son[u] == -1)return ;
    getpos(son[u], sp);
    for(int i=head[u]; i!=-1; i=edge[i].next){
        int v = edge[i].to;
        if(v != son[u] && v != fa[u])
            getpos(v, v); //轻儿子
    }
}

ll sum[N<<2];初始化
ll add[N<<2];


void PushUp(int rt){
    sum[rt] = sum[rt<<1]+sum[rt<<1|1];
}

void PushDown(int rt, int m){
    if(add[rt]){
        add[rt<<1] += add[rt];
        add[rt<<1|1] += add[rt];
        sum[rt<<1] += (m-(m>>1))*add[rt];
        sum[rt<<1|1] += (m>>1)*add[rt];
        add[rt] = 0;
    }
}

void build(int p, int re, int l, int r, int rt){
    add[rt] = 0;
    sum[rt] = 0;
    if(l == r){
        sum[rt] = re;
        return ;
    }
    int m=(l+r)>>1;
    if(p<=m)build(p, re, lson);
    else build(p, re, rson);
    PushUp(rt);
}

void update(int L, int R, int c, int l, int r, int rt){
    if(L <= l && r <= R){
        add[rt] += c;
        sum[rt] += (ll)c*(r-l+1);
        return ;
    }
    PushDown(rt, r-l+1);
    int m = (l+r)>>1;
    if(L <= m)update(L, R, c, lson);
    if(R > m)update(L, R, c, rson);
    PushUp(rt);
}

ll query(int L, int R, int l, int r, int rt){
    if(L <= l && r <= R){
        return sum[rt];
    }
    PushDown(rt, r-l+1);
    int m = (l+r)>>1;
    ll ret = 0;
    if(L <= m)ret += query(L, R, lson);
    if(R > m)ret += query(L, R, rson);
    return ret;
}

ll queryab(int u, int v){
    int f1 = top[u], f2 = top[v];
    ll tmp = 0;
    while(f1 != f2){
        if(dep[f1] < dep[f2]){  //对深度大的操作
            swap(f1, f2);
            swap(u, v);
        }
        tmp += query(p[f1], p[u], 1, pos, 1);
        u = fa[f1]; f1 = top[u];
    }
    if(u == v)return tmp;
    if(dep[u] > dep[v])swap(u, v);  //最后一次操作
    tmp += query(p[son[u]], p[v], 1, pos, 1);
    return tmp;
}

void updateab(int u, int v, int x){
    int f1 = top[u], f2 = top[v];
    while(f1 != f2){
        if(dep[f1] < dep[f2]){  //对深度大的操作
            swap(f1, f2);
            swap(u, v);
        }
        update(p[f1], p[u], x, 1, pos, 1);
        u = fa[f1]; f1 = top[u];
    }
    if(u == v)return ;
    if(dep[u] > dep[v])swap(u, v);  //最后一次操作
    update(p[son[u]], p[v], x, 1, pos, 1);
}

int e[N][3];
int  main(){
    int T;
    scanf("%d", &T);
    while(T--){
        init();
//        mst(sum, 0);   //初始化
        int n, q;
        scanf("%d%d", &n, &q);
        for(int i=1; i<n; i++){
            for(int j=0; j<3; j++)scanf("%d", &e[i][j]);
            addedge(e[i][0], e[i][1]);
            addedge(e[i][1], e[i][0]);
        }
        dfs1(1, -1, 0);
        getpos(1, 1);

        for(int i=1; i<n; i++){
            if(dep[e[i][0]] > dep[e[i][1]])swap(e[i][0], e[i][1]);
            build(p[e[i][1]], e[i][2], 1, pos, 1);
        }
        char op[10];
        while(q--){
            scanf("%s", op);
            if(op[0] == 'Q'){
                int u, v;
                scanf("%d%d", &u, &v);
                printf("%lld\n", queryab(u, v));
            }
            else{
                int u, v, x;
                scanf("%d%d%d", &u, &v, &x);
                updateab(u, v, x);
            }
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值