码迷,mamicode.com
首页 > 其他好文 > 详细

树链剖分

时间:2020-01-22 21:41:31      阅读:67      评论:0      收藏:0      [点我收藏+]

标签:+=   最短路   求和   line   mat   维护   线段树   防止   roo   

重链剖分

概念:

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

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

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

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

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

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

性质:

在轻边\((u,v)\)中,\(size(u)/2 \geqslant size(v)\)

从根结点到任意一点的路径上,不超过\(log\ n\)条轻链和\(log\ n\)条重链

时间复杂度为\(O(n\ log\ n)\)

先求出每个结点所在的子树大小,找到它的重儿子(即处理\(siz\)数组和\(son\)数组),记录其父亲以及深度(即处理\(fa\)数组和\(de\)数组)

\(code\)

void dfs_son(int x,int fath)
{
    siz[x]=1;
    fa[x]=fath;
    de[x]=de[fath]+1;
    for(int i=head[x];i;i=e[i].nxt)
    {
        int y=e[i].to;
        if(y==fath) continue;
        dfs_son(y,x);
        siz[x]+=siz[y];
        if(siz[son[x]]<siz[y]) son[x]=y;
    }
}

再将重儿子连接成重链,记录每个点所属重链的顶部端点(即处理\(top\)数组),记录每个结点的\(dfs\)序和\(dfs\)序所对应的结点(即处理\(dfn\)数组和\(rev\)数组)。为保证一条重链上各个结点\(dfs\)序连续,优先选择重儿子先\(dfs\)

\(code\)

void dfs_chain(int x,int tp)
{
    dfn[x]=++dfn_cnt;
    rev[dfn_cnt]=x;
    top[x]=tp;
    if(son[x]) dfs_chain(son[x],tp);//dfs重儿子
    for(int i=head[x];i;i=e[i].nxt)
    {
        int y=e[i].to;
        if(dfn[y]) continue;
        dfs_chain(y,y);//dfs轻儿子
    }
}

然后用数据结构(如线段树)来维护一条重链的信息

\(code\)

void pushup(int cur)
{
    val[cur]=val[ls[cur]]+val[rs[cur]];
}
void pushdown(int cur)
{
    if(!lazy[cur]) return ;
    lazy[ls[cur]]+=lazy[cur];
    lazy[rs[cur]]+=lazy[cur];
    val[ls[cur]]+=lazy[cur]*(r[ls[cur]]-l[ls[cur]]+1);
    val[rs[cur]]+=lazy[cur]*(r[rs[cur]]-l[rs[cur]]+1);
    lazy[cur]=0;
}
void build(int L,int R,int &cur)
{
    cur=++tree_cnt;
    l[cur]=L,r[cur]=R;
    if(L==R)
    {
        val[cur]=v[rev[L]];//将原结点的权值对应到dfs序上
        return ;
    }
    int mid=(l[cur]+r[cur])>>1;
    build(L,mid,ls[cur]);
    build(mid+1,R,rs[cur]);
    pushup(cur);
}
ll query(int L,int R,int cur)
{
    if(L<=l[cur]&&R>=r[cur]) return val[cur];
    pushdown(cur);
    ll sum=0;
    int mid=(l[cur]+r[cur])>>1;
    if(L<=mid) sum+=query(L,R,ls[cur]);
    if(R>mid) sum+=query(L,R,rs[cur]);
    return sum;
}
void modify(int L,int R,ll add,int cur)
{
    if(L<=l[cur]&&R>=r[cur])
    {
        val[cur]+=add*(r[cur]-l[cur]+1);
        lazy[cur]+=add;
        return ;
    }
    pushdown(cur);
    int mid=(l[cur]+r[cur])>>1;
    if(L<=mid) modify(L,R,add,ls[cur]);
    if(R>mid) modify(L,R,add,rs[cur]);
    pushup(cur);
}
ll sum(int x,int y)//类似与LCA的过程
{
    ll ans=0;
    while(top[x]!=top[y])
    {
        if(de[top[x]]<de[top[y]]) swap(x,y);//防止向上跳时跳过
        ans+=query(dfn[top[x]],dfn[x],root);//处理这条重链的贡献
        x=fa[top[x]];
    }
    if(dfn[x]>dfn[y]) swap(x,y);//循环结束,x和y已经在一条重链上
    return ans+query(dfn[x],dfn[y],root);//再加上两点间的贡献
}
void update(int x,int y,ll add)//与求和同理
{
    while(top[x]!=top[y])
    {
        if(de[top[x]]<de[top[y]]) swap(x,y);
        modify(dfn[top[x]],dfn[x],add,root);
        x=fa[top[x]];
    }
    if(dfn[x]>dfn[y]) swap(x,y);
    modify(dfn[x],dfn[y],add,root);
}

......

modify(dfn[x],dfn[x]+siz[x]-1,z,root);//将以x为根节点的子树内所有节点值都加上z
query(dfn[x],dfn[x]+siz[x]-1,root)//求以x为根节点的子树内所有节点值之和
update(x,y,z);//将树从x到y结点最短路径上所有节点的值都加上z
sum(x,y)//求树从x到y结点最短路径上所有节点的值之和

长链剖分

按深度剖分

\(maxde\),表示该节点为根的子树中的最大深度

可以用来求\(k\)次祖先和合并信息(HOT-HotelsDominant Indices

\(code\)

void dfs_son(int x,int fa)
{
    de[x]=maxde[x]=de[fa]+1;
    for(int i=head[x];i;i=e[i].nxt)
    {
        int y=e[i].to;
        if(y==fa) continue;
        dfs_son(y,x);
        maxde[x]=max(maxde[x],maxde[y]);
        if(maxde[son[x]]<maxde[y]) son[x]=y;
    }
}
int tmp[maxn],*f[maxn],*p=tmp;
void memery(int x)
{
    f[x]=p,p+=maxde[x]-de[x]+1;
}

树链剖分

标签:+=   最短路   求和   line   mat   维护   线段树   防止   roo   

原文地址:https://www.cnblogs.com/lhm-/p/12229481.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!