一棵树,支持:

  1. 路径加
  2. 单点查询

一般树上链的问题使用树链剖分解决。

重链剖分

前置知识

LCA,线段树

定义

重儿子:所有儿子中子树最大的儿子为重儿子

重边:重儿子之间的连边

重链:若干重儿子连成的链

性质

一棵树可以被剖成若干重链。

优先遍历重儿子,所有重链的dfs序连续。

重链数量不多于 \(\log_2\) 个。

一条链上经过的重链个数不多于 \(\log_2\) 个。

我们可以通过线段树维护一段重链的信息。

枚举链上的重链,查询可以做到 \(\log^2\)

实现

预处理

int fat[100001],siz[100001],dep[100001],hson[100001],top[100001],cnt,dfn[100001],dis[100001];
void getdfsh(int u,int fa) //获取:父节点,深度,子树大小,重儿子 
{
    fat[u]=fa;
    dep[u]=dep[fa]+1;
    int lll=0;
    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];
        if(v==fa) continue;
        getdfsh(v,u);
        if(siz[v]>lll)
        {
            hson[u]=v;
            lll=siz[v];
        }
        siz[u]+=siz[v];
    }
    siz[u]++;
}
void gettd(int u,int fa) //获取:链顶,dfs序 
{
    dfn[u] = ++cnt;
    dis[u] = cnt;
    if(hson[fat[u]]==u)
    {
        top[u]=top[fa];
    }
    else
    {
        top[u]=u;
    }
    if(hson[u]!=0) gettd(hson[u],u); //优先重子 
    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];
        if(v==fa||v==hson[u]) continue;
        gettd(v,u);
    }
}

修改与查询

int addlsum(int st,int ed,int v) //修改一条链,满足 lca(st,ed)=ed
{
	if(dfn[st]<dfn[ed]) swap(st,ed); //交换
	int u=st,re=0;
	while(1)
	{
		if(dfn[top[u]]<=dfn[ed]) //当前重链顶超过终点了
		{
			add(1,1,n,dfn[ed],dfn[u],v);
			break; //跳出
		}
		add(1,1,n,dfn[top[u]],dfn[u],v);
		u=fat[top[u]];
	}
	return re;
} 
int getlsum(int st,int ed) //查询一条链的和 同上
{
	if(dfn[st]<dfn[ed]) swap(st,ed);get(1,1,n,dfn[ed],dfn[st]);
	int u=st,re=0;
	while(1)
	{
		if(dfn[top[u]]<=dfn[ed])
		{
			re+=get(1,1,n,dfn[ed],dfn[u]);
			break;
		}
		re+=get(1,1,n,dfn[top[u]],dfn[u]);
		u=fat[top[u]];
	}
	return re;
}

长链剖分

长链即按深度剖分,主要解决深度问题,如 \(k\) 级祖先。

posted on 2023-05-04 18:25  lizhous  阅读(8)  评论(0编辑  收藏  举报