重链剖分

概念:

重儿子:父亲结点的所有儿子中子树结点数目最多(\(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结点最短路径上所有节点的值之和
posted @ 2020-01-22 20:11  lhm_liu  阅读(228)  评论(0编辑  收藏  举报