树链剖分(二)

上午刚打了一个树链剖分的模版题……
HDU:3966 Aragorn’s Story
题意:(多组数据)
给定一棵带权值树(节点数n<=50000,操作数m<=100000),支持2种操作:
1.查询某个点的权值;
2.对两个点路径的所有权值同时加或减一个数;
考虑到一次查询的正常复杂度O(n),一次更改的复杂度O(n),可知O(nm)超时。
O(mlog2n)可过。
考虑用一些数据结构,但是显然这里线段树什么的是不合理的,因为它不是一条链。
但是我们可以把一棵树砍成几条链,连起来组成一条长链。
这就是树链剖分的基本原理。
剖的方法:
轻重边划分:
size[x]定义为以x为子树的节点个数。
重儿子:当前节点下size值最大的儿子(若有size相等则随便连一个)。
重边:连接当前节点和其重孩子的边。
轻边:连接当前节点和除了重孩子的边。
需要使用到的变量有:

    tim//时间戳
    size[]//当前节点的size数
    son[]//记录当前节点的重儿子
    fa[]//记录当前节点的父亲
    dep[]//当前节点的深度
    top[]//当前重链的最顶部(距离根节点最近的点)
    tid[]//当前节点的时间
    Rank[]//当前时间对应着哪个节点

在树链剖分时,首先我们要确定每一个节点的size从而确定它的重儿子,然后连成重边。
之后我们再把重边都连起来拉成重链。
这个过程需要两边dfs。

void dfs1(int rt,int f,int depth){//确定重儿子
    fa[rt]=f;//记录父亲
    dep[rt]=depth;//记录深度
    size[rt]=1;//size初始化为1(即节点自身)
    for(int i=head[rt];~i;i=edge[i].next){//遍历邻接表
        int v=edge[i].to;
        if(v!=f){//避免指回父亲
            dfs1(v,rt,depth+1);
            size[rt]+=size[v];//统计当前节点从孩子能获得的size
            if(son[rt]==-1||size[son[rt]]<size[v])//如果没有重孩子||size值大于原来重孩子
                son[rt]=v;
        }
    }   
}
void dfs2(int rt,int tp){//tp代表顶端
    top[rt]=tp;
    tid[rt]=++tim;
    Rank[tim]=rt;
    if(son[rt]==-1)return;
    dfs2(son[rt],tp);//同条重链上,顶点相同
    for(int i=head[rt];~i;i=edge[i].next){
        int v=edge[i].to;
        if(v!=fa[rt]&&v!=son[rt])
            dfs2(v,v);
    }
}

tid&&Rank——个人认为最不好懂的树链剖分部分
树链剖分是按照时间来建造整棵树的,也就意味着,当我们想要找一个节点在当前链上的位置时,
tid数组起到了至关重要的作用,因为tid[x]=tim,而tim对应的就是当前的链上的位置。
而Rank数组则是在当前时刻对应的节点。
所以有:
x=Rank[tid[x]];
理解了这个之后,我们可以用tid来找出某个节点对应的整条链的位置,用Rank找出某个位置对应的树的哪个节点,这样,整棵树就彻彻底底可以转成链了。
但是有些东西在树链剖分中不能想当然,比如有个东西叫LCA……
一开始我天真地以为更改树上的两点间路径上的所有权值,那么就可以这样:
找出他们的LCA然后把一条路径分成两条链进行更改,用线段树进行维护。
现在想想……真是天真的很……
因为:
两遍dfs后形成的整条长链中的每个元素,或是短链或是单点,都来自于一整条重链或者是轻边。
而LCA……并不一定与那两个点形成一整条重链或者一条轻边(也就是,混搭)。
所以说……用LCA这个东西来进行区间统计亦或是区间修改就是在坑自己。(至少这道题是在坑自己)

路径区间操作时请用dep[ ]进行奇奇怪怪的操作!

区间修改的思路:
x,y之间的路径进行区间修改。

void chage(){
while(x,y不在一条重链上){
    dep[top[x]]<dep[top[y]]?swap(x,y):1;
    对x---top[x]所在的链进行修改;
    x=fa[top[x]];
    }
    对x---y所在的链进行修改。
}

是时候该发本题的代码了……
本题应注意两点,第一点,多组数据,第二点,HDU的爆栈(请自行扩充栈)
扩充栈:#pragma comment(linker, “/STACK:1024000000,1024000000”)

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#define N 50005
#define lc rt<<1,l,mid
#define rc rt<<1|1,mid+1,r
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;
int n,m,p,cnt=0,tim=0;
int son[N],dep[N],Rank[N],size[N],tid[N],fa[N],head[N<<1],w[N],top[N],sum[N<<2],lazy[N<<2];
struct Edge{
    int next,to;
}edge[N<<1];
void save(int u,int v){
    edge[cnt].next=head[u];
    edge[cnt].to=v;
    head[u]=cnt++;
}
void dfs1(int rt,int f,int depth){
    fa[rt]=f;
    dep[rt]=depth;
    size[rt]=1;
    for(int i=head[rt];~i;i=edge[i].next){
        int v=edge[i].to;
        if(v!=f){
            dfs1(v,rt,depth+1);
            size[rt]+=size[v];
            if(son[rt]==-1||size[son[rt]]<size[v])
                son[rt]=v;
        }
    }
}
void dfs2(int rt,int tp){
    top[rt]=tp;
    tid[rt]=++tim;
    Rank[tim]=rt;
    if(son[rt]==-1)return;
    dfs2(son[rt],tp);
    for(int i=head[rt];~i;i=edge[i].next){
        int v=edge[i].to;
        if(v!=son[rt]&&v!=fa[rt])
            dfs2(v,v);
    }
}
//线段树部分
void build(int rt,int l,int r){
    lazy[rt]=0;
    if(l==r){
        sum[rt]=w[Rank[l]];
        return;
    }
    int mid=(l+r)>>1;
    build(lc);
    build(rc);
}
void pushdown(int rt){
    if(lazy[rt]){
        lazy[rt<<1]+=lazy[rt];
        lazy[rt<<1|1]+=lazy[rt];
        sum[rt<<1]+=lazy[rt];
        sum[rt<<1|1]+=lazy[rt];
        lazy[rt]=0;
    }
}
void modify(int rt,int l,int r,int lm,int rm,int modi){
    if(l>rm||r<lm)return;
    if(l>=lm&&r<=rm){
        lazy[rt]+=modi;
        sum[rt]+=modi*(r-l+1);
        return;
    }
    int mid=(l+r)>>1;
    pushdown(rt);
    if(mid>=lm)modify(lc,lm,rm,modi);
    if(mid<rm)modify(rc,lm,rm,modi);
}
void Change(int x,int y,int val)  //在剖好的链上进行的更改
{  
    while(top[x]!=top[y])  
    {  
        if(dep[top[x]]<dep[top[y]]) swap(x,y);  
        modify(1,1,n,tid[top[x]],tid[x],val);    
        x=fa[top[x]];  
    }  
    if(dep[x]>dep[y]) swap(x,y);  
    modify(1,1,n,tid[x],tid[y],val);  
}  
int query(int rt,int l,int r,int q){
    if(l==r)return sum[rt];
    int mid=(l+r)>>1;
    pushdown(rt);
    int ans=0;
    if(mid>=q)ans=query(lc,q);
    if(mid<q)ans=query(rc,q);
    return ans;
}
int main (){
    while(~scanf("%d%d%d",&n,&m,&p)){
        memset(head,-1,sizeof(head));
        memset(edge,0,sizeof(edge));
        cnt=tim=0;
        memset(son,-1,sizeof(son));
        for(int i=1;i<=n;i++)
            scanf("%d",&w[i]);
        for(int i=1;i<=m;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            save(u,v);save(v,u);
        }
        dfs1(1,0,0);
        dfs2(1,1);
        build(1,1,n);
        for(int i=1;i<=p;i++){
            char c[5];
            scanf("%s",c);
            if(c[0]=='Q'){
                int q;
                scanf("%d",&q);
                printf("%d\n",query(1,1,n,tid[q]));
            }
            else {
                int c1,c2,cg;
                scanf("%d%d%d",&c1,&c2,&cg);
                if(c[0]=='D')
                    Change(c1,c2,-cg);
                else 
                    Change(c1,c2,cg);
            }
        }
    }
    return 0;
}

至此,一道简单的树链剖分模版题分析完毕!
请注意,”简单“一词修饰的是”模版题“,而不是树链剖分。(严肃脸)

你可能感兴趣的:(数据结构)