树链剖分

 在学树链剖分前 先回顾两个算法

树上差分

树上差分,是一个适用于树上区间操作的算法 它是差分数组,前缀和求解的树上拓展。

其中 树上差分 99%的可能性与LCA一起出现在题目中

那么关于树上差分的问题来了 

1.如何给树上的一条链(x~y)加上1(x为y的祖先)

还是要回到差分数组考虑 设原数组为a 差分数组为d 

假设给d[i]+1 就相当于给a[i]~a[n]+1

那么如果给树上的一个节点x d[x]+1 就只能是x到根节点这条链+1 即d[x]+1 相当于a[root]~a[x]+1

所以区间修改就是d[fa[x]]-1,d[y]+1 (fa[x]为x的父亲) 即 a[y]~a[root]+1 a[fa[x]]~a[root]-1

2.如何给树上的任意一条链(x~y)加上1

我们可以找到x y的公共祖先lca(x,y) 将这条链拆开 变成x~lca(x,y) y~lca(x,y)两条链

但是这个时候发现 我们给lca(x,y)节点+2了 所以我们可以在第二次减的时候 只让lca(x,y)的父亲节点到根节点-1

所以最后 树上修改就变成了:

d[x]+1  d[y]+1  d[lca(x,y)]−1  d[fa[lca(x,y)]]−1

LCA

对于一棵树 求两个节点的最近公共祖先

如图 1和6的LCA是8 11和1的LCA是8 11和15的LCA是4 14和13的LCA是1

LCA有在线和离线两种算法

1.Tarjan:

Tarjan算法基于dfs 在dfs的过程中 对于每个节点的位置的询问做出相应的回答

dfs的过程中 当一棵子树被搜索完成之后 就把他和他的父亲节点合并成同一集合 在搜索当前子树节点的询问时 如果该询问的另一个节点已经被访问过 那么该编号的询问时被标记了的 于是输出当前状态下 另一个节点所在并查集的祖先 如果另一个节点还没被访问过 那么做下标记 继续dfs

比如8-1-14-13 此时8已经完成了对子树1的子树14的dfs与合并 如果查询时存在询问(13,14) 则其LCA即fa(14) 然后处理完由13和已经完成搜索的子树的询问 然后合并子树13的集合与fa(13)的集合 回溯到fa(13) 并dfs完所有1的其他未被搜索过的儿子 并完成子树1的所有节点的合并 再往fa(1)回溯

树上最短路:

const int maxn = 40005;
int father[maxn],ans[maxn],dis[maxn];
int vis[maxn];
int u,v;
int tot,head[maxn];

struct Edge
{
    int dest;
    int next;
    int weight;
} edge[maxn*2];

vector<int> query[maxn],query_id[maxn];

void add(int u,int v,int l)
{
    edge[++tot].dest=v;
    edge[tot].next=head[u];
    edge[tot].weight=l;
    head[u]=tot;
}

int find(int x)
{
    if(father[x]==x)
        return x;
    return father[x]=find(father[x]);
}

void Tarjan(int x)
{
    vis[x]=1;
    for(int i=head[x]; i!=0; i=edge[i].next)
    {
        int y=edge[i].dest;
        if(vis[y])
            continue;
        dis[y]=dis[x]+edge[i].weight;
        Tarjan(y);
        father[y]=x;
    }
    for(int i=0; i<query[x].size(); i++)
    {
        int y=query[x][i],id=query_id[x][i];
        if(vis[y]==2)
        {
            int lca=find(y);
            ans[id]=min(ans[id],dis[x]+dis[y]-2*dis[lca]);
        }
    }
    vis[x]=2;
}

int main()
{
    int T,n,m;
    scanf("%d",&T);
    while(T--)
    {
        memset(head,-1,sizeof(head));
        memset(vis,0,sizeof(vis));
        scanf("%d %d",&n,&m);
        for(int i=1; i<=n; i++)
        {
            father[i]=i;
            query[i].clear();
            query_id[i].clear();
        }
        tot=0;
        for(int i=1; i<n; i++)
        {
            int x,y,z;
            scanf("%d %d %d",&x,&y,&z);
            add(x,y,z),add(y,x,z);
        }
        for(int i=1; i<=m; i++)
        {
            int x,y;
            scanf("%d %d",&x,&y);
            if(x==y)
                ans[i]=0;
            else
            {
                query[x].push_back(y),query_id[x].push_back(i);
                query[y].push_back(x),query_id[y].push_back(i);
                ans[i]=1<<30;
            }
        }
        Tarjan(1);
        for(int i=1; i<=m; ++i)
            printf("%d\n",ans[i]);
    }
}

 2.倍增

struct node
{
    int t,nex;
} e[500001<<1];
int depht[500001],father[500001][22],lg[500001],head[500001];
int tot;
inline void add(int x,int y)
{
    e[++tot].t=y;
    e[tot].nex=head[x];
    head[x]=tot;
}
inline void dfs(int now,int fath)
{
    depht[now]=depht[fath]+1;
    father[now][0]=fath;
    for(register int i=1; (1<<i)<=depht[now]; ++i)
        father[now][i]=father[father[now][i-1]][i-1];
    for(register int i=head[now]; i; i=e[i].nex)
    {
        if(e[i].t!=fath)
            dfs(e[i].t,now);
    }
}
inline int lca(int x,int y)
{
    if(depht[x]<depht[y])
        swap(x,y);
    while(depht[x]>depht[y])
        x=father[x][lg[depht[x]-depht[y]]-1];
    if(x==y)
        return x;
    for(register int k=lg[depht[x]]; k>=0; --k)
        if(father[x][k]!=father[y][k])
            x=father[x][k],y=father[y][k];
    return father[x][0];
}
int n,m,s;
int main()
{
    scanf("%d%d%d",&n,&m,&s);
    for(register int i=1; i<=n-1; ++i)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y);
        add(y,x);
    }
    dfs(s,0);
    for(register int i=1; i<=n; ++i)
        lg[i]=lg[i-1]+(1<<lg[i-1]==i);
    for(register int i=1; i<=m; ++i)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        printf("%d\n",lca(x,y));
    }
    return 0;
}

 

那么如果将x~y+1 求x~y最短路径上节点之和 变成一道题目的两种操作 那么显然 上面两种方法都不能用了

树链剖分的作用:

树剖是通过轻重边剖分将树分割成多条链 然后利用数据结构来维护这些链 本质是一种优化后的暴力

基础概念:

重儿子:父亲节点的所有儿子中子树结点数目最多的

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

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

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

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

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

f[u]:保存节点u的父亲节点

d[u]:保存节点u的深度值

size[u]:保存以u为根的子树节点个数

son[u]:保存重儿子

rank[u]:保存当前dfs标号在树中对应的节点

top[u]:保存当前节点所在链的顶端节点

id[u]:保存树中每个节点剖分以后的新编号(DFS的执行顺序)

树链剖分的实现:

 

1.对于一个点我们首先求出它所在子树的大小,找出他的重儿子(即处理处size son数组)

 例子:

对于点1 它有三个儿子 2,3,4

2 所在的子树大小是5;3 所在的子树大小是2;4 所在子树的大小是6 那么1的重儿子是4

如果一个点的多个儿子所在子树大小相等且最大 那随便找一个当做它的重儿子

叶子节点没有重儿子 非叶节点有且只有一个重儿子

2.在dfs过程中顺便记录其父亲以及深度(即处理出f,d数组) 操作1,2可以通过一遍dfs完成

void dfs1(int u,int f,int deep) //当前节点、父节点、层次深度
{
    f[u]=fa,d[u]=deep,size[u]=1;
    for(int i=head[u];i;i=edge[i].next)
    {
        int v=edge[i].dest;
        if(v==fa) continue;
        dfs1(v,u,deep+1);
        size[u]+=size[v];
        if(size[v]>size[son[u]]) son[u]=v;
    }
}

 

3.第二遍dfs,连接重链,同时标记每一个节点的dfs序,并且为了用数据结构维护重链,我们在dfs时保证一条重链上各个节点dfs序连接(即处理出top,id,rank数组)

void dfs2(int u,int t) //当前节点、重链顶端
{
    top[u]=t;
    id[u]=++cnt; //标记dfs序
    rank[cnt]=u; //序号cnt对应节点u
    if(!son[u]) return;
    dfs2(son[u],t); //我们选择优先进入重儿子来保证一条重链上各店dfs连续 
                    //一个点和它的重儿子处于同一条重链 所以重儿子所在重链
                    //的顶端还是t
    for(int i=head[u];i;i=edge[i].next)
    {
        int v=edge[i].dest;
        if(v!=son[u]&&v!=f[u])
            dfs2(v,v); //一个点位于轻链底端,那么它的top必然是它本身
    }
}

 

4.两遍dfs就是树链剖分的主要处理 通过dfs我们已经保证一条重链上各个节点dfs序连续,那么可以想到,我们可以通过数据结构来维护一条重链的信息

比如之前的那个题目 修改和查询的操作原理是类似的 以查询操作为例 其实就是个LCA 不过这里是用了top来进行加速 因为top可以直接跳转到该重链的起始节点 轻链没有起始节点之说,他们的top就是自己。需要注意的是,每次循环只能跳一次,并且让节点深的那个来跳到top的位置

int sum(int x,int y)
{
    int ans=0,fx=top[x],fy=top[y];
    while(fx!=fy) //如果两点不在同一条重链
    {
        if(d[fx]>=d[fy])
        {
            ans+=query(id[fx],id[x],rt); //线段树区间求和,处理这条重链的贡献
            x=f[fx],fx=top[x]; //将x设置成原链头的父亲节点,走轻边,继续循环
        }
        else
        {
            ans+=query(id[fy],id[y],rt);
            y=f[fy],fy=top[y];
        }
    }
    //循环结束,两点位于同一重链上,但两点不一定为同一点,所以我们还要统计这两点的贡献
    if(id[x]<=id[y])
        ans+=query(id[x],id[y],rt);
    else 
        ans+=query(id[y],id[x],rt);
    return ans;
}

 

转载于:https://www.cnblogs.com/CSUSTJCC/p/11326152.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值