树链剖分(二)

来源:互联网 发布:浙江大学软件学院 878 编辑:程序博客网 时间:2024/05/29 11:28

上午刚打了一个树链剖分的模版题……
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;}

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

0 0
原创粉丝点击