树链剖分

来源:互联网 发布:网络助手 红米 编辑:程序博客网 时间:2024/06/18 09:09
//树链剖分 + 树状数组 + 点权修改//HDU 3966#pragma comment(linker, "/STACK:1024000000 , 1024000000")#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>#include<vector>using namespace std ;const int maxn = 1e5 + 10 ;struct Edge{    //链式前向星节点,u节点的子节点to和下一个节点为止next     int to , next ;}edge[maxn * 2] ;//链式前向星head数组 int head[maxn] , tot ;//top表示i节点所在重链中最浅的节点的编号,注意是在树中的编号,而不是在树状数组的编号 int top[maxn] ;//表示i节点的父节点编号 int fa[maxn] ;//表示i节点的深度,根节点为0深度int deep[maxn] ;//表示i节点为根的子树中节点的个数int num[maxn] ;//表示树中编号为i的节点在树状数组c里面的编号为p[i] int p[maxn] ; //表示树上编号为i的节点的重儿子的编号int son[maxn] ;//表示树状数组的位置,初始化为1,因为使用的是树状数组的结构int pos ;//树状数组+ 树链剖分 + 点权修改和查询 //树状数组在后一个节点添加一个前一个节点的更改至的相反数得到的树状数组获得第i个数的值的方法就有sum[i] - sum[i-1] 变成了sum[i],具体为什么我也不知道,知识画出来是这样啊 //区间修改,求最值求和//线段树|划分树|树状数组 //树上点权边权修改,最值求和操作//树链剖分 //构建树链剖分,即将树哈希到线性结构,比如梳妆数组和线段树的数组里面 //将树形结构哈希到树状数组的结构实际上是对树形结构按照重儿子优先的原则重新标号(p数组的作用),转换到树状数组c数组里面去,通过树状数组来维护点权sum//将树形结构哈希到树状数组,得到哈希对应的下边转换数组p void init(){    //初始化     tot = 0 ;    memset( head , -1 , sizeof( head )) ;    pos = 1 ;    memset( son , -1 , sizeof( son )) ;}//链式前向星void addedge(int u , int v){    edge[tot].to = v ;    edge[tot].next = head[u] ;    head[u] = tot ++ ;} //第一次dfs求u节点的父节点,深度,子节个数,重子节点void dfs1( int u , int pre , int d){    //u当前结点编号,pre当前结点的父节点,d当前结点的深度    deep[u] = d ;    fa[u] = pre ;    num[u] = 1 ;    for(int i = head[u] ; i != -1 ; i = edge[i].next ){        int v = edge[i].to ;        //收入的时候没有区分u的父节点,现在以1为根节点后,u有了父节点,所以,父节点不能访问他         if( v != pre){            dfs1( v , u , d + 1) ;            num[u ] += num[v] ;            //回溯,通过num更新u的重儿子            //初始化son为-1,son取u节点的子节点里面总子节点最大的那个子节点             if( son[u] == -1 || num [v] > num[son[u]])                son[u] = v ;         }    } } //第二次dfs求u所在重链中最浅的节点的编号,重链可以是一条边也可以是多条边的路径,总之就是路径//另外将树的节点哈希到树状数组 void dfs2( int u , int sp){    top[u] = sp ;    p[u] = pos ++ ;    if( son[u] == -1) return ;    //优先给重儿子分配树状数组的编号     dfs2 ( 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])            dfs2( v , v) ;    }} //树状数组维护树形结构的点权int lowbit( int x ){    return x&( -x )  ;} int c[maxn] ;int n ;int sum( int i ){    int s = 0 ;    while( i > 0 ){        s += c[i] ;        i -= lowbit(i ) ;    }     return s  ;}void add( int i , int val){    while( i <= n ){        c[i] += val ;        i += lowbit( i ) ;    }}//修改树形结构的点权//u , v 表示u->v的路径,val表示修改的值 void change( int u , int v , int val){    //u和v节点的重链的浅节点     int f1 = top[u] , f2 = top[v] ;    //f1和f2不相同表示两个处于不同的重链上,    //对于非一条重链上的两个节点u,v,一种循环到两个节点处于一条重链上为止    //这样处理会让两个节点 在相交的位置处于同一条链     while( f1 != f2){        if( deep[f1] < deep[f2]){            swap( f1 , f2 ) ;            swap(  u , v ) ;        }        //求改较深的那条重链        add( p[f1] , val ) ;        add( p[u] + 1 , -val) ;        //取重链的父节点 , 父节点所在的重链中浅节点位置f1         u = fa[f1] ;        f1 = top[ u ] ;     }     if( deep[u] > deep[v]) swap ( u , v ) ;    //更新节点值,现在他们一定处于同一条重链,修改这条链上的节点值    add( p[u] , val ) ;    add( p[v] + 1 , -val ) ; } int a[maxn] ;int main(){    int M , P ;    while( scanf("%d%d%d" , & n , & M , & P ) == 3){        int u , v ;        int c1 , c2 , k ;        char op[10] ;        //树链剖分         init() ;        for(int i = 1 ;i<= n ;i++){            scanf("%d" , & a[i]) ;        }        while( M --){            scanf("%d%d" , &u , & v) ;            addedge( u , v ) ;            addedge( v , u ) ;        }        dfs1( 1 , 0 , 0 ) ;        dfs2( 1 , 1 ) ;        //树状数组        memset( c, 0 , sizeof( c )) ;        for(int i = 1 ;i<= n ;i++){            //树形结构第i位置的是a[i] ,该位置被哈希到c数组的第p[i] 个位置            add( p[i] , a[i]) ;            add( p[i] + 1 , -a[i]) ;         }         while( P-- ){            scanf("%s" , op) ;            if( op[0] == 'Q'){                scanf("%d" , & u) ;                printf("%d\n" , sum( p[u])) ;            }            else{                scanf("%d%d%d" , & c1 , & c2 , & k );                if( op[0] == 'D')                    k = - k ;                change( c1 , c2 , k ) ;            }        }    }    return 0 ;}
//树链剖分 + 线段树 + 边权维护//线段树和树链剖分实际上维护了树形结构,利用p数组将树形结构的点或者边哈希到线段树或者树链剖分的区间,实现利用线段树和树状数组对树形结构的边和点进行修改和查询//线段树 | 树状数组 | 划分树 = 区间求和、求最值、求k大,区间值修改//线段树 | 树状数组 + 树链剖分 = 树形结构路径求和、求最值,点权和边权修改 //SPOJ 375. Query on a tree  #include <stdio.h>#include <string.h>#include <iostream>#include <algorithm>#include <vector>#include <queue>#include <set>#include <map>#include <string>#include <math.h>#include <stdlib.h>using namespace std;const int MAXN = 10010;struct Edge{    int to,next;}edge[MAXN*2];int head[MAXN],tot;int top[MAXN];//top[v]表示v所在的重链的顶端节点int fa[MAXN]; //父亲节点int deep[MAXN];//深度int num[MAXN];//num[v]表示以v为根的子树的节点数int p[MAXN];//p[v]表示v与其父亲节点的连边在线段树中的位置int son[MAXN];//重儿子int pos;void init(){    tot = 0;    memset(head,-1,sizeof(head));    pos = 0;    memset(son,-1,sizeof(son));}void addedge(int u,int v){    edge[tot].to = v;edge[tot].next = head[u];head[u] = tot++;}void dfs1(int u,int pre,int d){    deep[u] = d;    fa[u] = pre;    num[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);            num[u] += num[v];            if(son[u] == -1 || num[v] > num[son[u]])                son[u] = v;        }    }}void getpos(int u,int sp) //第二遍dfs求出top和p{    top[u] = sp;    if(son[u] != -1)    {        p[u] = pos++;        getpos(son[u],sp);    }    else {        p[u] = pos++;        return;    }    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 Node{    int l,r;    int Max;}segTree[MAXN*3];void build(int i,int l,int r){    segTree[i].l = l;    segTree[i].r = r;    segTree[i].Max = 0;    if(l == r)return;    int mid = (l+r)/2;    build(i<<1,l,mid);    build((i<<1)|1,mid+1,r);}void push_up(int i){    segTree[i].Max = max( segTree[i<<1].Max , segTree[ (i<<1) | 1 ].Max);}//更新线段树的第k个值为val//p表示树形结构的第i个节点在线段树的位置 //i表示更新的区间,k表示更新的小区间的边界 void update(int i,int k,int val){    if(segTree[i].l == k && segTree[i].r == k){        segTree[i].Max = val;        return;    }    int mid = (segTree[i].l + segTree[i].r) / 2;    if(k <= mid)update(i<<1,k,val);    else update( ( i<<1 ) | 1 , k , val);    push_up(i);}//查询线段树中[l,r] 的最大值int query(int i,int l,int r){    if(segTree[i].l == l && segTree[i].r == r)        return segTree[i].Max;    int mid = (segTree[i].l + segTree[i].r)/2;    if(r <= mid)return query(i<<1,l,r);    else if(l > mid)return query((i<<1)|1,l,r);    else return max(query(i<<1,l,mid),query((i<<1)|1,mid+1,r));}//查询u->v路径的边的最大值int find(int u,int v){    int f1 = top[u], f2 = top[v];    int tmp = 0;    while(f1 != f2){        if(deep[f1] < deep[f2]){            swap(f1,f2);            swap(u,v);        }        //query查询最深的那条重链的最值         tmp = max(tmp,query(1,p[f1],p[u]));        //u和f1更新为最深的重链的上一条重链的值         u = fa[f1]; f1 = top[u];    }    //u==v有两种情况,一种是本身传进来u==v,第二种比如u是v的父节点,那么开始的时候他们的重链的最浅的节点不相等,但是一旦完成循环我们他们就相等了     if(u == v)return tmp;    if(deep[u] > deep[v]) swap(u,v);    //如果u,v处于同一条重链,那么直接在线段树里面找最大值,然后对temp取最大,因为在u,v之间可能存在多条重链,temp存储其他重链中的最大值     return max(tmp,query(1,p[son[u]],p[v]));}int e[MAXN][3];int main(){    int T;    int n;    scanf("%d",&T);    while(T--){        //树链剖分,将树形结构的节点编号按照重子节点优先的顺序哈希到线段树的区间节点上         init();        scanf("%d",&n);        for(int i = 0;i < n-1;i++){            scanf("%d%d%d",&e[i][0],&e[i][1],&e[i][2]);            addedge(e[i][0],e[i][1]);            addedge(e[i][1],e[i][0]);        }        dfs1(1,0,0);        getpos(1,1);        //线段树维护树形结构,如果要对树形结构进行查询和修改,只需要对线段树进行查询和修改         build(1,0,pos-1);        for(int i = 0;i < n-1; i++){            //如果第i条边的左子节点的深度比右子节点大,交换节点,保证,左子节点深度比右子节点深度小,保证节点更新不重复             if(deep[e[i][0]] > deep[e[i][1]])                swap(e[i][0],e[i][1]);            // 更新线段树的第k个值为val             //使用点权代表边权, 将u到v的边用uv间深度最大的那个节点代表,并由那个节点建立线段树,也就是说,有多少条边就建立多少个节点             update(1,p[ e[i][1] ] , e[i][2]);        }        char op[10];        int u,v;        while(scanf("%s",op) == 1){            if(op[0] == 'D')break;            scanf("%d%d",&u,&v);            if(op[0] == 'Q')                printf("%d\n",find(u,v));            //第k条边的边权被k的深度最大的点的点权代替并建立了线段树             else update(1,p[ e[u-1][1] ],v);        }    }    return 0;}
原创粉丝点击