树链剖分笔记

树链剖分是维护树上路径的一种有力工具。支持以下操作:
修改:单点改权,单边改权,从u到v的简单路径上的点或边改权等等
修改权值的方式可以是加/减/乘/除一个值。
查询:单点查询,单边查询,查询从u到v的简单路径上的点或边权值之和/最大值/最小值等等

树链剖分的主要步骤:
1、找到重儿子son,顺便维护出树的fa,size等等。重儿子就是当前节点的所有儿子中size最大的那个;
2、根据重儿子划分出轻重边,当前节点向重儿子连的边是重边。重边相连形成重链,两条分开的重链被轻边相连;
3、把每条重链作为一个连续区间映射到数据结构中,轻边作为一个单位区间映射到数据结构中;
4、对于每次修改用数据结构维护,查询用数据结构查询。
常用的数据结构是线段树。

对一棵树进行树链剖分后,树的性质:
从u到根的简单路径上最多只有logn条轻边,logn条重链。
证明:设当前节点为u,轻儿子为lightson,那么size[lightson]<=size[u]/2,size[root]=n,size[lightson]>=1,那么可以发现,u到lightson的简单路径上,最多只有logn个轻儿子,那么最多只有logn条轻边。又因为重链被轻边相连,那么最多有logn条重链。
那么可以发现一次修改的时间复杂度是O(log^2n)(如果使用线段树维护)。

具体实现:
1、dfs第一次预处理size,fa,son;
2、dfs第二次预处理top(轻重链的顶端),p(当前点映射到数据结构里面的位置),fp(数据结构里面的点映射到树里面的位置),并且用dfs保证重儿子被映射到连续区间;
3、构建数据结构;
4、修改的时候,如果两个点不在一条重链上面,那么就调整深度大的点使其往它所在链的顶端走(用数据结构维护),然后通过fa数组跳到下一条链上,直到两个点在一条重链上。
5、对于在一条重链上的点,用数据结构维护。
6、查询同修改。
贴一个HDU3966的代码
树上区间修改单点查询

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 50010
using namespace std;
struct node{
    int v,next;
}edge[N<<1];
struct segtree{
    int l,r,val;
}tree[N<<2];
int fa[N],son[N],top[N],p[N],sz[N],v[N],head[N],dep[N],cnt,tot,n,m,k,a,b,c;
char op[3];
void addedge(int u,int v)
{
    edge[cnt].v=v;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}
void dfs1(int u,int f)
{
    if(u<=0)return;
    int maxch=0;
    sz[u]=1;
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].v;
        if(v!=f)
        {
            fa[v]=u;
            dep[v]=dep[u]+1;
            dfs1(v,u);
            sz[u]+=sz[v];
            if(sz[v]>maxch)
            {
                maxch=sz[v];
                son[u]=v;
            }
        }
    }
}
void dfs2(int u,int t)
{
    top[u]=t;
    p[u]=++tot;
    if(son[u]==0)return;
    dfs2(son[u],t);
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].v;
        if(v!=fa[u]&&v!=son[u])
            dfs2(v,v);
    }
}
void build(int x,int l,int r)
{
    tree[x].l=l,tree[x].r=r;
    if(l==r)return;
    build(2*x,l,(l+r)/2);
    build(2*x+1,(l+r)/2+1,r);
}
void change(int x,int l,int r,int v)
{
    if(tree[x].l==l&&tree[x].r==r)
    {
        tree[x].val+=v;
        return;
    }
    if(l>tree[x].r&&r<tree[x].l)return;
    if(r<=(tree[x].l+tree[x].r)/2)change(2*x,l,r,v);
    else if(l>(tree[x].l+tree[x].r)/2)change(2*x+1,l,r,v);
    else
    {
        change(2*x,l,(tree[x].l+tree[x].r)/2,v);
        change(2*x+1,(tree[x].l+tree[x].r)/2+1,r,v);
    }
}
int query(int x,int u)
{
    if(tree[x].l==u&&tree[x].r==u)return tree[x].val;
    if(u<=(tree[x].l+tree[x].r)/2)return tree[x].val+query(2*x,u);
    else if(u>(tree[x].l+tree[x].r)/2)return tree[x].val+query(2*x+1,u);
}
void changepath(int u,int v,int val)
{
    while(top[u]!=top[v])
    {
        if(dep[top[u]]<dep[top[v]])swap(u,v);
        change(1,p[top[u]],p[u],val);
        u=fa[top[u]];
    }
    if(dep[u]<dep[v])swap(u,v);
    change(1,p[v],p[u],val);
}
int main()
{
    while(~scanf("%d%d%d",&n,&m,&k))
    {
        memset(tree,0,sizeof tree);
        memset(son,0,sizeof son);
        memset(top,0,sizeof top);
        memset(head,-1,sizeof head);
        cnt=tot=0;
        for(int i=1;i<=n;++i)
            scanf("%d",&v[i]);
        for(int i=1;i<=m;++i)
        {
            scanf("%d%d",&a,&b);
            addedge(a,b);
            addedge(b,a);
        }
        fa[1]=1;
        dep[1]=1;
        dfs1(1,0);
        dfs2(1,1);
        build(1,1,tot);
        for(int i=1;i<=k;++i)
        {
            scanf("%s",op);
            if(op[0]=='I')
            {
                scanf("%d%d%d",&a,&b,&c);
                changepath(a,b,c);
            }
            else if(op[0]=='D')
            {
                scanf("%d%d%d",&a,&b,&c);
                changepath(a,b,-c);
            }
            else
            {
                scanf("%d",&a);
                printf("%d\n",query(1,p[a])+v[a]);
            }
        }
    }
}

你可能感兴趣的:(数据结构,树,图论,树链剖分)