树链剖分 学习笔记

前置知识:$dfs$序,线段树

---------------------------------------

我们可以回顾两个问题:

1.树上从$s$到$t$的路径,每个点权值加上$z$。

很简单。遍历整棵树即可。

2.求树上$s$到$t$的权值和。

$LCA$可做。可以利用$LCA$的性质$dis[s]+dis[t]-2*dis[lca]$做即可。时间复杂度$O(n\log n)$。

但是把这两个问题结合起来呢?

每次更改权值后都要重新算一遍$dis$。那么时间复杂度变成$n^2$的了。这时候,我们就需要树链剖分来解决此类问题。

--------------------------------------

树链剖分:把一棵树划分成若干链,转化成若干序列,并用数据结构维护的算法。

概念:

重儿子:父亲节点的所有儿子中子树结点数目最多($size$最大)的结点;

轻儿子:父亲节点中除了重儿子以外的儿子;

重边:父亲结点和重儿子连成的边;

轻边:父亲节点和轻儿子连成的边;

重链:由多条重边连接而成的路径;

轻链:由多条轻边连接而成的路径;

----------------------------------------------------

变量声明:

int size[maxn],son[maxn],dep[maxn],fa[maxn];//大小、重儿子、深度、父亲节点
int top[maxn],dfn[maxn],cnt;//所在重链的顶点、时间戳

由上面的定义可知,重链一定是由轻边连接的。下面我们对树链剖分的复杂度进行证明。

因为每次跳树跳的是轻边,所以每跳一次后,所在树的子树的大小一定至少是原来的二倍。所以单次跳树的复杂度是$O(n\log n)$。总复杂度$O(n\log^2 n)$。

两次$dfs$

第一次$dfs$是进行$dfs$序,把每个结点的子树大小,父亲节点,重儿子和深度处理出来。

inline void dfs_son(int now,int f,int deep)
{
    dep[now]=deep;
    size[now]=1;
    fa[now]=f;
    int maxson=-1;
    for (int i=head[now];i;i=edge[i].next)
    {
        int to=edge[i].to;
        if (to==f) continue;
        dfs_son(to,now,deep+1);
        size[now]+=size[to];
        if (size[to]>maxson) maxson=size[to],son[now]=to;
    }
}

第二次$dfs$是记录时间戳,按照优先遍历重儿子的原则,把树处理成若干链。

inline void dfs(int now,int topf)
{
    dfn[now]=++cnt;
    wt[cnt]=w[now];
    top[now]=topf;
    if (!son[now]) return;
    dfs(son[now],topf);
    for (int i=head[now];i;i=edge[i].next)
    {
        int to=edge[i].to;
        if (dfn[to]) continue;
        dfs(to,to);
    }
}

然后进行线段树建树。代码不贴了。

更新操作:

如果$s$和$t$不在一个链内,那么我们要进行跳树操作。先把当前链的顶端到此结点的路径权值处理了,然后跳到该链顶点的父亲节点。类似于$LCA$的操作。

inline void updrange(int x,int y,int k)
{
    k%=mod;
    while(top[x]!=top[y])
    {
        if (dep[top[x]]<dep[top[y]]) swap(x,y);
        update(1,dfn[top[x]],dfn[x],k);
        x=fa[top[x]];
    }
    if (dep[x]>dep[y]) swap(x,y);
    update(1,dfn[x],dfn[y],k);
}
inline int qrange(int x,int y)
{
    int ans=0;
    while(top[x]!=top[y])
    {
        if (dep[top[x]]<dep[top[y]]) swap(x,y);
        ans+=query(1,dfn[top[x]],dfn[x]);
        ans%=mod;
        x=fa[top[x]];
    }
    if (dep[x]>dep[y]) swap(x,y);
    ans+=query(1,dfn[x],dfn[y]);
    ans%=mod;
    return ans;
}

如果要更新子树,那么就更简单了,只有一行代码:

update(1,dfn[x],dfn[x]+size[x]-1);

至此,树链剖分的所有内容已讲解完毕。

练习题:

1.【模板】轻重链剖分

2.【ZJOI2008】树的统计

3.【NOI2015】软件包管理器

4.【HAOI2015】树上操作

5.【JLOI2014】松鼠的新家

都不难,稍微变通一下就是树链剖分的模板题。

模板代码(练习题1):

#include
#define int long long
using namespace std;
const int maxn=200005;
int n,m,r,mod;
int size[maxn],son[maxn],fa[maxn],dep[maxn],w[maxn];
int top[maxn],wt[maxn],dfn[maxn],cnt;
int head[maxn*2],jishu;
struct node
{
    int next,to;
}edge[maxn*2];
int lazy[maxn*4];
struct tre
{
    int l,r,v;
}tree[maxn*5];
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void add(int from,int to)
{
    edge[++jishu].next=head[from];
    edge[jishu].to=to;
    head[from]=jishu;
}
inline void dfs_son(int now,int f,int deep)
{
    dep[now]=deep;
    size[now]=1;
    fa[now]=f;
    int maxson=-1;
    for (int i=head[now];i;i=edge[i].next)
    {
        int to=edge[i].to;
        if (to==f) continue;
        dfs_son(to,now,deep+1);
        size[now]+=size[to];
        if (size[to]>maxson) maxson=size[to],son[now]=to;
    }
}
inline void dfs(int now,int topf)
{
    dfn[now]=++cnt;
    wt[cnt]=w[now];
    top[now]=topf;
    if (!son[now]) return;
    dfs(son[now],topf);
    for (int i=head[now];i;i=edge[i].next)
    {
        int to=edge[i].to;
        if (dfn[to]) continue;
        dfs(to,to);
    }
}
inline void build(int index,int l,int r)
{
    tree[index].l=l;
    tree[index].r=r;
    if (l==r)
    {
        tree[index].v=wt[l];
        tree[index].v%=mod;
        return;
    }
    int mid=(l+r)>>1;
    build(index*2,l,mid);
    build(index*2+1,mid+1,r);
    tree[index].v=(tree[index*2].v+tree[index*2+1].v)%mod;
}
void pushdown(int index)
{
    lazy[index*2]+=lazy[index];
    lazy[index*2+1]+=lazy[index];
    tree[index*2].v+=lazy[index]*(tree[index*2].r-tree[index*2].l+1);
    tree[index*2+1].v+=lazy[index]*(tree[index*2+1].r-tree[index*2+1].l+1);
    tree[index*2].v%=mod;
    tree[index*2+1].v%=mod;
    lazy[index]=0;
}
inline void update(int index,int l,int r,int k)
{
    if (l<=tree[index].l&&tree[index].r<=r) {lazy[index]+=k;tree[index].v+=k*(tree[index].r-tree[index].l+1);}
    else{
        if (lazy[index]) pushdown(index);
        int mid=(tree[index].l+tree[index].r)>>1;
        if (l<=mid) update(index*2,l,r,k);
        if (r>mid) update(index*2+1,l,r,k);
        tree[index].v=(tree[index*2].v+tree[index*2+1].v)%mod;
    }
}
inline int query(int index,int l,int r)
{
    if (l<=tree[index].l&&tree[index].r<=r) return tree[index].v;
    else{
        if (lazy[index]) pushdown(index);
        int mid=(tree[index].r+tree[index].l)>>1,res=0;
        if (l<=mid) res+=query(index*2,l,r);
        if (r>mid) res+=query(index*2+1,l,r);
        return res;
    }
}
inline void updrange(int x,int y,int k)
{
    k%=mod;
    while(top[x]!=top[y])
    {
        if (dep[top[x]]<dep[top[y]]) swap(x,y);
        update(1,dfn[top[x]],dfn[x],k);
        x=fa[top[x]];
    }
    if (dep[x]>dep[y]) swap(x,y);
    update(1,dfn[x],dfn[y],k);
}
inline int qrange(int x,int y)
{
    int ans=0;
    while(top[x]!=top[y])
    {
        if (dep[top[x]]<dep[top[y]]) swap(x,y);
        ans+=query(1,dfn[top[x]],dfn[x]);
        ans%=mod;
        x=fa[top[x]];
    }
    if (dep[x]>dep[y]) swap(x,y);
    ans+=query(1,dfn[x],dfn[y]);
    ans%=mod;
    return ans;
}
inline void updson(int x,int k)
{
    k%=mod;
    update(1,dfn[x],dfn[x]+size[x]-1,k);
}
inline int qson(int x)
{
    return query(1,dfn[x],dfn[x]+size[x]-1);
}
signed main()
{
    n=read(),m=read(),r=read(),mod=read();
    for (int i=1;i<=n;i++) w[i]=read();
    for (int i=1;i)
    {
        int x=read(),y=read();
        add(x,y);add(y,x);
    }
    dfs_son(r,0,1);
    dfs(r,r);
    build(1,1,n);
    for (int i=1;i<=m;i++)
    {
        int flag=read(),x,y,z;
        if (flag==1)
        {
            x=read(),y=read(),z=read();
            updrange(x,y,z);
        }
        if (flag==2)
        {
            x=read(),y=read();
            printf("%lld\n",qrange(x,y)%mod);
        }
        if (flag==3)
        {
            x=read(),y=read();
            updson(x,y);
        }
        if (flag==4)
        {
            x=read();
            printf("%lld\n",qson(x)%mod);
        }
    }
    return 0;
}

 

你可能感兴趣的:(树链剖分 学习笔记)