树链剖分

主要思想:

对一棵树进行重链和轻边的划分,并用数据结构对重链进行维护,达到在树上快速维护和查询的目的。

剖分方法:

定义:

           重儿子:一个节点的儿子子树中节点最多的儿子为重儿子(只有一个,相等就任取其一)
           重边:父节点与重儿子连的边
           轻边:除重边外的树边
           重链:连续的重边连成的链

然后就有一个性质:

从任意一个节点到根所经过的重链和轻边的个数都不到$log_n$个(n为树的大小)

证明:

从根开始每经过一个轻边,子树的大小就至少减少一半(轻边连的子树大小一定小于原树的一半),所以轻边个数$\le log_n$
重链和重链之间至少有一条轻边相隔,否则可以将两个重链合并,得到重链个数$\le$轻边个数$\le log_n$

用线段树等数据结构对重链进行维护就使对树的查询和维护复杂度小于O(${log_n}^2$)

代码实现

记录数组

struct Edge{
    int u,v,next,dis;
}e[maxn*2];
int edge_s,N,stree;//edge_s为边数,N是树的大小,stree要辅助记录p 
int depth[maxn],fa[maxn],size[maxn],son[maxn],top[maxn],p[maxn],fp[maxn],fi[maxn],ma[maxn*3],mi[maxn*3],lazy[maxn*3];
/*
e和fi[]: 邻接表存边 
depth[u]: 来保存当前节点u的深度
fa[u]: 用来保存当前节点u的父亲
size[u]: 用来保存以u为根的子树节点个数
son[u]: 用来保存当前节点u的重儿子
top[u]: 用来保存当前节点u的所在链的顶端节点
p[u]: 用来保存当前节点u在线段树中的位置
fp[]: 用来保存线段树相应位置保存的是当前哪个节点
ma[],mi[]: 线段树节点维护数值 
lazy[]: 线段树懒惰标记 
*/

第一次dfs

void dfs1(int x,int pre,int d){//第一遍dfs求出fa,depth,size,son
    depth[x]=d,fa[x]=pre,size[x]=1;
    for(int i=fi[x];i;i=e[i].next){
        int v=e[i].v;
        if(v==pre)continue;
        dfs1(v,x,d+1);size[x]+=size[v];
        if(size[v]>size[son[x]]||!son[x])son[x]=v;//目前的重儿子 
    }
}

第二次dfs

void dfs2(int x,int sp){//第二遍dfs求出top和p
    top[x]=sp,p[x]=++stree,fp[stree]=x;
    if(!son[x])return;
    dfs2(son[x],sp);
    for(int i=fi[x];i;i=e[i].next){
        int v=e[i].v;
        if(v==fa[x]||v==son[x])continue;
        dfs2(v,v);
    }
}

查找操作(这里查找路径最大值)

int query(int x,int l,int r,int L,int R){//线段树区间最值查询 
    if(l==r)return ma[x];
    if(L<=l&&R>=r)return ma[x];
    push_down(x);
    int mid=(l+r)>>1,tmp=-999999999;
    if(mid>=L)tmp=query(x<<1,l,mid,L,R);
    if(mid<R)tmp=max(tmp,query(x<<1|1,mid+1,r,L,R));
    return tmp;
} 
inline int find(int u,int v){//查询u->v路径上边权的最大值
    int f1=top[u],f2=top[v];
    int tmp=-999999999;
    while(f1!=f2){
        if(depth[f1]<depth[f2]){
            tmp = max(tmp, query(1,1,N,p[f2],p[v]));
            v = fa[f2],f2 = top[v];
        }
        else{
            tmp = max(tmp, query(1,1,N,p[f1],p[u]));
            u = fa[f1],f1 = top[u];
        }
    }
    if(u==v)return tmp;
    if(depth[u]>depth[v])swap(u,v);
    return max(tmp,query(1,1,N,p[son[u]],p[v]));
}

单个树边修改

inline void change_val(int x,int l,int r,int k,int val){//线段树单点修改 
    if(l==r){
        mi[x]=ma[x]=val;
        return;
    }
    push_down(x);
    int mid=(l+r)>>1;
    if(mid>=k)change_val(x<<1,l,mid,k,val);
    else change_val(x<<1|1,mid+1,r,k,val);
    update(x);
}
inline void change(int x,int y){//修改第x条边为y 
    int u=e[x<<1].u,v=e[x<<1].v;
    if(depth[u]<depth[v])change_val(1,1,N,p[v],y);
    else change_val(1,1,N,p[u],y);
}

路径修改(这里为路径取反)

void update_ne(int x,int l,int r,int L,int R){//线段树区间取反 
    int tmp;
    if(l==r){
        tmp=ma[x],ma[x]=-mi[x],mi[x]=-tmp;
        return;
    }
    if(l>=L&&r<=R){
        tmp=ma[x],ma[x]=-mi[x],mi[x]=-tmp,lazy[x]^=1;
        return;
    }
    push_down(x);
    int mid=(l+r)>>1;
    if(mid>=L)update_ne(x<<1,l,mid,L,R);
    if(mid<R)update_ne(x<<1|1,mid+1,r,L,R);
    update(x);
}
void negate(int u,int v){//路径取反 
    int f1=top[u],f2=top[v];
    while(f1!=f2){
        if(depth[f1]<depth[f2]){
            update_ne(1,1,N,p[f2],p[v]);
            v = fa[f2];
            f2 = top[v];
        }
        else{
            update_ne(1,1,N,p[f1],p[u]);
            u = fa[f1];
               f1 = top[u];
        }
    }
    if(u==v)return;
    if(depth[u]>depth[v])swap(u,v);
    update_ne(1,1,N,p[son[u]],p[v]);
}

求最近公共祖先

inline int get_lca(int u,int v){//求u,v的lca
    int f1=top[u],f2=top[v];
    while(f1!=f2){
        if(depth[f1]<depth[f2]){
            v = fa[f2],f2 = top[v];
        }
        else{
            u = fa[f1],f1 = top[u];
        }
    }
    if(u==v)return u;
    if(depth[u]>depth[v])swap(u,v);
    return u;
}

 

上面用到的标记下放,更新操作

inline void update(int x){//线段树更新 
    ma[x]=max(ma[x<<1],ma[x<<1|1]),mi[x]=min(mi[x<<1],mi[x<<1|1]);
}
inline void push_down(int x){//lazy标记下放 
    int tmp;
    if(lazy[x]){
        tmp=ma[x<<1],ma[x<<1]=-mi[x<<1],mi[x<<1]=-tmp,
        tmp=ma[x<<1|1],ma[x<<1|1]=-mi[x<<1|1],mi[x<<1|1]=-tmp,
        lazy[x<<1]^=1,lazy[x<<1|1]^=1,lazy[x]=0;
    }
}

例题

poj 3237

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 15050
struct Edge{
    int u,v,next,dis;
}e[maxn*2];
int edge_s,N,stree;//edge_s为边数,N是树的大小,stree要辅助记录p 
int depth[maxn],fa[maxn],size[maxn],son[maxn],top[maxn],p[maxn],fp[maxn],fi[maxn],ma[maxn*3],mi[maxn*3],lazy[maxn*3];
/*
e和fi[]: 邻接表存边 
depth[u]: 来保存当前节点u的深度
fa[u]: 用来保存当前节点u的父亲
size[u]: 用来保存以u为根的子树节点个数
son[u]: 用来保存当前节点u的重儿子
top[u]: 用来保存当前节点u的所在链的顶端节点
p[u]: 用来保存当前节点u在线段树中的位置
fp[]: 用来保存线段树相应位置保存的是当前哪个节点
ma[],mi[]: 线段树节点维护数值 
lazy[]: 线段树懒惰标记 
*/
void add_edge(int u,int v,int dis){
    e[++edge_s].next=fi[u],e[edge_s].v=v,e[edge_s].u=u,fi[u]=edge_s,e[edge_s].dis=dis;
    e[++edge_s].next=fi[v],e[edge_s].v=u,e[edge_s].u=v,fi[v]=edge_s,e[edge_s].dis=dis;
}
void dfs1(int x,int pre,int d){//第一遍dfs求出fa,depth,size,son
    depth[x]=d,fa[x]=pre,size[x]=1;
    for(int i=fi[x];i;i=e[i].next){
        int v=e[i].v;
        if(v==pre)continue;
        dfs1(v,x,d+1);size[x]+=size[v];
        if(size[v]>size[son[x]]||!son[x])son[x]=v;//目前的重儿子 
    }
}
void dfs2(int x,int sp){//第二遍dfs求出top和p
    top[x]=sp,p[x]=++stree,fp[stree]=x;
    if(!son[x])return;
    dfs2(son[x],sp);
    for(int i=fi[x];i;i=e[i].next){
        int v=e[i].v;
        if(v==fa[x]||v==son[x])continue;
        dfs2(v,v);
    }
}
inline void update(int x){//线段树更新 
    ma[x]=max(ma[x<<1],ma[x<<1|1]),mi[x]=min(mi[x<<1],mi[x<<1|1]);
}
inline void push_down(int x){//lazy标记下放 
    int tmp;
    if(lazy[x]){
        tmp=ma[x<<1],ma[x<<1]=-mi[x<<1],mi[x<<1]=-tmp,
        tmp=ma[x<<1|1],ma[x<<1|1]=-mi[x<<1|1],mi[x<<1|1]=-tmp,
        lazy[x<<1]^=1,lazy[x<<1|1]^=1,lazy[x]=0;
    }
}
int query(int x,int l,int r,int L,int R){//线段树区间最值查询 
    if(l==r)return ma[x];
    if(L<=l&&R>=r)return ma[x];
    push_down(x);
    int mid=(l+r)>>1,tmp=-999999999;
    if(mid>=L)tmp=query(x<<1,l,mid,L,R);
    if(mid<R)tmp=max(tmp,query(x<<1|1,mid+1,r,L,R));
    return tmp;
} 
inline int find(int u,int v){//查询u->v路径上边权的最大值
    int f1=top[u],f2=top[v];
    int tmp=-999999999;
    while(f1!=f2){
        if(depth[f1]<depth[f2]){
            tmp = max(tmp, query(1,1,N,p[f2],p[v]));
            v = fa[f2],f2 = top[v];
        }
        else{
            tmp = max(tmp, query(1,1,N,p[f1],p[u]));
            u = fa[f1],f1 = top[u];
        }
    }
    if(u==v)return tmp;
    if(depth[u]>depth[v])swap(u,v);
    return max(tmp,query(1,1,N,p[son[u]],p[v]));
}
inline void change_val(int x,int l,int r,int k,int val){//线段树单点修改 
    if(l==r){
        mi[x]=ma[x]=val;
        return;
    }
    push_down(x);
    int mid=(l+r)>>1;
    if(mid>=k)change_val(x<<1,l,mid,k,val);
    else change_val(x<<1|1,mid+1,r,k,val);
    update(x);
}
inline void change(int x,int y){//修改第x条边为y 
    int u=e[x<<1].u,v=e[x<<1].v;
    if(depth[u]<depth[v])change_val(1,1,N,p[v],y);
    else change_val(1,1,N,p[u],y);
}
void update_ne(int x,int l,int r,int L,int R){//线段树区间取反 
    int tmp;
    if(l==r){
        tmp=ma[x],ma[x]=-mi[x],mi[x]=-tmp;
        return;
    }
    if(l>=L&&r<=R){
        tmp=ma[x],ma[x]=-mi[x],mi[x]=-tmp,lazy[x]^=1;
        return;
    }
    push_down(x);
    int mid=(l+r)>>1;
    if(mid>=L)update_ne(x<<1,l,mid,L,R);
    if(mid<R)update_ne(x<<1|1,mid+1,r,L,R);
    update(x);
}
void negate(int u,int v){//路径取反 
    int f1=top[u],f2=top[v];
    while(f1!=f2){
        if(depth[f1]<depth[f2]){
            update_ne(1,1,N,p[f2],p[v]);
            v = fa[f2];
            f2 = top[v];
        }
        else{
            update_ne(1,1,N,p[f1],p[u]);
            u = fa[f1];
               f1 = top[u];
        }
    }
    if(u==v)return;
    if(depth[u]>depth[v])swap(u,v);
    update_ne(1,1,N,p[son[u]],p[v]);
}
int work(){
    int a,b,d;char s[20];
    scanf("%d",&N);
    for(int i=1;i<N;i++){
        scanf("%d%d%d",&a,&b,&d);
        add_edge(a,b,d);
    }
    dfs1(1,0,1);dfs2(1,1);
    for(int i=1;i<edge_s;i+=2){
        int u=e[i].u,v=e[i].v;
        if(depth[u]<depth[v])change_val(1,1,N,p[v],e[i].dis);
        else change_val(1,1,N,p[u],e[i].dis);
    }
//    change_val(1,1,N,p[1],-999999999);
    while(~scanf("%s",s)){
        if(s[0]=='D')return 0;
        scanf("%d%d",&a,&b);
        if(s[0]=='C'){
            change(a,b);
        }
        else if(s[0]=='N'){
            negate(a,b);
        }
        else{
            printf("%d\n",find(a,b));
        }
    }
    return 0;
}
void init(){
    stree=edge_s=0;
    memset(son,0,sizeof(son)),memset(fi,0,sizeof(fi)),memset(lazy,0,sizeof(lazy));
}
int main(){
//    freopen("maintaintree.in","r",stdin);
//    freopen("maintaintree.out","w",stdout);
    int t;scanf("%d",&t);
    while(t--){
        init();
        work();
    }
    return 0;
}

 SPOJ QTREE - Query on a tree

#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
#define maxn 10005
#define inf 0x3f3f3f3f
struct Edge{
    int next,dis,to;
}edge[maxn<<1];
int n,se,fi[maxn];
int ma[maxn*3],depth[maxn],st,p[maxn],top[maxn],fa[maxn],son[maxn],size[maxn];
/*
*p[u]记录u在线段树的位置
depth[u]记录u的深度
top[u]记录u所在的重链的顶端
son[u]记录u的重儿子
size[u]记录u子树的大小
*/
inline void add_edge(int u,int v,int w){
    edge[++se].next=fi[u],edge[se].dis=w,edge[se].to=v,fi[u]=se,
    edge[++se].next=fi[v],edge[se].dis=w,edge[se].to=u,fi[v]=se;
}
inline void init(){
    se=1,memset(fi,0,sizeof(fi)),st=0,memset(son,0,sizeof(son));
}
void dfs1(int x,int f,int d){//求出depth,fa,size,son 
    depth[x]=d,fa[x]=f,size[x]=1;
    for(int i=fi[x];i;i=edge[i].next){
        int v=edge[i].to;
        if(v==f)continue;
        dfs1(v,x,d+1),size[x]+=size[v];
        if(size[v]>size[son[x]])son[x]=v;
    }
}
void dfs2(int x,int t){//求出p,top 
    top[x]=t,p[x]=++st;
    if(!son[x])return;
    dfs2(son[x],t);
    for(int i=fi[x];i;i=edge[i].next){
        int v=edge[i].to;
        if(v==fa[x]||v==son[x])continue;
        dfs2(v,v);
    }
}
void change_val(int x,int l,int r,int L,int val){//线段树单点修改 
    if(l==r){
        ma[x]=val;return;
    }
    int mid=(l+r)>>1;
    if(mid>=L)change_val(x<<1,l,mid,L,val);
    else change_val(x<<1|1,mid+1,r,L,val);
    ma[x]=max(ma[x<<1],ma[x<<1|1]);
}
inline void change(int x,int w){//将第i条边权值改为w 
    int u=edge[(x<<1)^1].to,v=edge[x<<1].to;
    if(depth[u]>depth[v])change_val(1,1,n,p[u],w);
    else change_val(1,1,n,p[v],w);
}
int query_val(int x,int l,int r,int L,int R){//线段树区间查询 
    if(L<=l&&R>=r)return ma[x];
    int mid=(l+r)>>1,tmp=-inf;
    if(L<=mid)tmp=max(tmp,query_val(x<<1,l,mid,L,R));
    if(R>mid)tmp=max(tmp,query_val(x<<1|1,mid+1,r,L,R));
    return tmp;
}
inline int query(int u,int v){//路径查询 
    int f1=top[u],f2=top[v],tmp=-inf;
    while(f1!=f2){
        if(depth[f1]<depth[f2]){
            tmp=max(tmp,query_val(1,1,n,p[f2],p[v])),
            v=fa[f2],f2=top[v];
        }
        else{
            tmp=max(tmp,query_val(1,1,n,p[f1],p[u])),
            u=fa[f1],f1=top[u];
        }
    }
    if(u==v)return tmp;
    if(depth[u]>depth[v])swap(u,v);
    return max(tmp,query_val(1,1,n,p[son[u]],p[v]));
}
inline void work(){
    init();scanf("%d",&n);
    int u,v,w;char s[10];
    for(int i=1;i<n;i++){
        scanf("%d%d%d",&u,&v,&w),add_edge(u,v,w);
    }
    dfs1(1,0,1),dfs2(1,1);
    for(int i=2;i<se;i+=2){
        int u=edge[i^1].to,v=edge[i].to;
        if(depth[u]<depth[v])change_val(1,1,n,p[v],edge[i].dis);
        else change_val(1,1,n,p[u],edge[i].dis);
    }
    while(~scanf("%s",s)){
        if(s[0]=='D')break;
        scanf("%d%d",&u,&v);
        if(s[0]=='C'){
            change(u,v);
        }
        else{
            printf("%d\n",query(u,v));
        }
    }
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--)work();
    return 0;
}

 

posted @ 2017-11-17 09:41  Bennettz  阅读(149)  评论(0编辑  收藏  举报