【专题】树链剖分精讲

什么是树链剖分?

指一种对树进行划分的算法,它先通过轻重边剖分将树分为多条链,保证每个点属于且只属于一条链,然后再通过数据结构(树状数组、SBT、SPLAY、线段树等)来维护每一条链,主要用来维护树上每条链的极值或和之类的。

类似

首先把树上倍增摆在前面,如果不会树上倍增就不必来看树链剖分。

为什么要学树链剖分?

有的人说:“我会树上倍增,我怕谁?”,没错,你怕的就是树链剖分。
意思是:树上倍增能做的,树链剖分都能做;树链剖分能做的,树上倍增不一定能做。树上倍增虽然能求树上区间极值或和,但一旦有树上边或点权值修改的操作,您就死翘翘了~这时我们需要一个新的算法来补上我们的树上倍增操作的漏洞,没错,就是树链剖分!没错!你学了它一定不会后悔!!

少废话,正题如下:

首先,概念必须明白:

轻儿子&重儿子

对于一个节点,在它的所有儿子中,size(子树大小)最大的一个就是重儿子,其余的都是轻儿子。重儿子所在的树链叫做重链,其余叫做轻链

这里写图片描述
图中共有5条重链,分别是:
1—-2—-6—-3
5
3
4—-7
8.

我们需要维护什么?

  • tree数组,表示整棵树上的信息
  • tree[i].depth表示i的的深度
  • tree[i].top表示i所在的重链顶端的结点
  • tree[i].father表示i点的父亲
  • tree[i].key表示i点的权值
  • tree[i].To_tree表示节点i在线段树中的位置
  • tree[i].size表示以i为根的子树大小
  • tree[i].heavy_son表示i节点的重儿子
  • To_num[i]表示线段树中的i号节点对应的是原树中的位置
    //鉴于我是个蒟蒻,这里之讲用数据结构线段树维护

    怎么维护?

    第一次dfs,我们从根开始遍历整棵树,然后记录每个点的【depth,heavy_son,size,father,key】(dfs)。

CODE:

int dfs(int t)
{
    int y,mx=0;
    tree[t].size=1;
    bz[t]=true;
    for (int i=last[t];i;i=next[i])
    {
        y=tov[i];
        if(!tree[y].dep)
        {
            tree[y].depth=tree[t].depth+1;
            tree[y].father=t;
            tree[y].key=len[i];
            dfs(y);
            tree[t].size+=tree[y].size;
            if(tree[y].size>mx)
            {
                mx=tree[y].size;
                tree[t].heavy_son=y;
            }
        }
    }
}

人工栈CODE:

void biuld_tree()
{
    int i,x,y;
    tot=1;
    d[1].t=root;tree[1].depth=1;
    for (i=1;i<=n;++i) cur[i]=last[i];
    while(tot)
    {
        x=d[tot].t;
        i=cur[x];
        while(tree[tov[i]].depth) i=next[i];
        if(!i)
        {
            d[tot--].maxx=0;
            ++tree[x].size;
            tree[d[tot].t].size+=tree[x].size;
            if(tree[x].size>d[tot].maxx) d[tot].maxx=tree[x].size,tree[d[tot].t].hs=x;
            continue;
        }
        cur[x]=next[i];
        y=tov[i];
        tree[y].fath=x;
        tree[y].depth=tree[x].depth+1;
        num[y]=number[i];
        d[++tot].t=y;       
    }       
}

操作:

  • 建树
  • 查询
    查询包括
    LCA查询
    区间答案查询

建树

建树的思想就是dfs,每次先搜索重儿子,保证重链上的每个点在线段树的位置是连续的。维护出每个点的【top,To_tree】以及To_num。

CODE:

void build_dfs(int t,int k)
{
    int y;
    bz[t]=true;
    tree[t].top=k;
    tree[t].To_tree=++tot;
    To_num[tot]=t;
    if(tree[t].heavy_son) dfs2(tree[t].heavy_son,k);
    for (int i=last[t];i;i=next[i])
    {
        y=tov[i];
        if(y!=tree[t].father&&y!=tree[t].heavy_son) build_dfs(y,y);
    }
}

人工栈CODE:

void complete_tree()
{
    int i,x,y;
    tot=1;sz=1;
    d[1].t=root;d[1].topp=root;
    tree[1].To_tree=1;tree[1].top=1;
    bz[0]=1;to_num[1]=1;
    for (i=1;i<=n;++i) cur[i]=last[i];
    while(tot)
    {
        x=d[tot].t;
        tree[x].top=d[tot].topp;
        bz[x]=true;
        if(!bz[tree[x].heavy_son])
        {
            tree[tree[x].heavy_son].top=d[tot].topp;
            d[++tot].t=tree[x].heavy_son;
            d[tot].topp=tree[tree[x].heavy_son].top;    
            tree[tree[x].heavy_son].To_tree=++sz;
            to_num[sz]=tree[x].heavy_son;
            continue;           
        }
        i=cur[x];
        while(bz[tov[i]]&&i) i=next[i];
        if(!i)
        {
            d[tot--].topp=0;
            continue;
        }
        cur[x]=next[i];
        y=tov[i];
        tree[tree[x].hs].top=d[tot].topp;
        tree[y].To_tree=++sz;
        to_num[sz]=y;
        d[++tot].t=y;d[tot].topp=y;     
    } 
}

查询

LCA查询

绝对比树上倍增的查询简单!
我们查询x,y的LCA,使用树链剖分。
STEPS

  1. 我们查看当前tree[x].top是否与tree[y].top相等
  2. 如果不相等,那么就将深度较大的(假设为x)跳到当前重链顶端的父亲。
  3. 一直这样迭代,直到它们top相等为止。
  4. 如果它们的top相等,有两种情况:
    ①x=y
    ②y是x的祖先(或x是y的祖先)

那么LCA就是深度较小的那一个!
是不是很简单~

CODE:

int query_LCA(int x,int y)
{
    while(tree[x].top!=tree[y].top)
    {
        if(tree[tree[x].top].depth>tree[tree[y].top].depth) x=tree[tree[x].top].father;
        else y=tree[tree[y].top].father;
    }
    return tree[x].depth<=tree[y].depth?x:y;
}

区间极值或和查询

合理运用线段树等数据结构,在寻找LCA过程中,顺带处理区间极值或和的查询。

CODE:

int query_ans(int x,int y)
{
    int ans=INF;
    while(tree[x].top!=tree[y].top)
    {
        if(tree[tree[x].top].depth>tree[tree[y].top].depth) 
        {
            ans=min(ans,find(1,n,1,tree[tree[x].top].To_tree,tree[x].To_tree));
            x=tree[tree[x].top].father;
        }
        else
        {
            ans=min(ans,find(1,n,1,tree[tree[y].top].To_tree,tree[y].To_tree));
            y=tree[tree[y].top].father; 
        }
    }
    if(x==y) return ans;
    if(tree[x].depth<tree[y].depth) ans=min(ans,find(1,n,1,tree[tree[x].heavy_son].To_tree,tree[y].To_tree));
    else ans=min(ans,find(1,n,1,tree[tree[y].heavy_son].To_tree,tree[x].To_tree));
    return ans;
}

本CODE为查询链上最小值

基本知识讲完了,重在理解,你明白了吗?

来道例题:

【NOIP2013提高组day1】货车运输

Time Limits: 1000 ms Memory Limits: 131072 KB Detailed Limits

Description

A 国有 n 座城市,编号从 1 到 n,城市之间有 m 条双向道路。每一条道路对车辆都有重量限制,简称限重。现在有 q 辆货车在运输货物,司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。

Input

第一行有两个用一个空格隔开的整数 n,m,表示 A 国有 n 座城市和 m 条道路。
接下来 m 行每行 3 个整数 x、y、z,每两个整数之间用一个空格隔开,表示从 x 号城市到 y 号城市有一条限重为 z 的道路。注意:x 不等于 y,两座城市之间可能有多条道路。
接下来一行有一个整数 q,表示有 q 辆货车需要运货。
接下来 q 行,每行两个整数 x、y,之间用一个空格隔开,表示一辆货车需要从 x 城市运输货物到 y 城市,注意:x 不等于 y。

Output

输出共有 q 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。如果货车不能到达目的地,输出-1。

Sample Input

4 3
1 2 4
2 3 3
3 1 1
3
1 3
1 4
1 3

Sample Output

3
-1
3

Data Constraint

对于 30%的数据,0 < n < 1,000,0 < m < 10,000,0 < q < 1,000;
对于 60%的数据,0 < n < 1,000,0 < m < 50,000,0 < q < 1,000;
对于 100%的数据,0 < n < 10,000,0 < m < 50,000,0 < q < 30,000,0 ≤ z ≤ 100,000。

给点时间思考~
考虑~
我们讲的是树链剖分,想想怎么才能和它挂钩~
~
有想法吗?

好,讲正解:

很显然,首先我们要做一个最大生成树,用于方便我们求最小值的最大值~这个没有问题吧。
然后就可以两种选择,因为没有权值修改,所以直接用倍增做也是可以的。
当然我们现在讲树链剖分。
如果你刚刚听懂了的话,现在应该直到这棵树应该怎么剖~

CODE:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
struct Moon{int top,To_tree,heavy_son,father,depth,size;long long key;};
struct Candy{int x,y,z;};
int father[10001];
Moon tree[10001];
Candy a[50001];
int n,m,q,tot;
int To_num[1000001];
int next[50001],last[50001],len[50001],tov[50001];
long long f[1000001];
bool bz[10001];
bool cmp(Candy a,Candy b){return a.z>b.z;}
void insert(int x,int y,int z)
{
    tov[++tot]=y;
    len[tot]=z;
    next[tot]=last[x];
    last[x]=tot;
}
int getfather(int x)
{
    if(father[x]==x) return x;
    father[x]=getfather(father[x]);
    return father[x];
}
void dfs(int t)
{
    int y,mx=0;
    tree[t].size=1;
    bz[t]=true;
    for (int i=last[t];i;i=next[i])
    {
        y=tov[i];
        if(!tree[y].depth)
        {
            tree[y].depth=tree[t].depth+1;
            tree[y].father=t;
            tree[y].key=len[i];
            dfs(y);
            tree[t].size+=tree[y].size;
            if(tree[y].size>mx)
            {
                mx=tree[y].size;
                tree[t].heavy_son=y;
            }
        }
    }
}
void dfs2(int t,int k)
{
    int y;
    bz[t]=true;
    tree[t].top=k;
    tree[t].To_tree=++tot;
    To_num[tot]=t;
    if(tree[t].heavy_son)dfs2(tree[t].heavy_son,k);
    for (int i=last[t];i;i=next[i])
    {
        y=tov[i];
        if(y!=tree[t].father&&y!=tree[t].heavy_son) dfs2(y,y);
    }
}
void buildtree(int l,int r,int v)
{
    if(l==r)
    {
        f[v]=tree[To_num[l]].key;
        return;
    }
    int mid=(l+r)/2;
    buildtree(l,mid,v*2);
    buildtree(mid+1,r,v*2+1);
    f[v]=min(f[v*2],f[v*2+1]);
}
long long find(int l,int r,int v,int x,int y)
{
    if(l==x&&r==y) return f[v];
    int mid=(l+r)/2;
    if(y<=mid) return find(l,mid,v*2,x,y);
    else if(x>mid) return find(mid+1,r,v*2+1,x,y);
    else return min(find(l,mid,v*2,x,mid),find(mid+1,r,v*2+1,mid+1,y));
}
long long query_ans(int x,int y)
{
    long long ans=9187201950435737471;
    while(tree[x].top!=tree[y].top)
    {
        if(tree[tree[x].top].depth>tree[tree[y].top].depth) 
        {
            ans=min(ans,find(1,n,1,tree[tree[x].top].To_tree,tree[x].To_tree));
            x=tree[tree[x].top].father;
        }
        else
        {
            ans=min(ans,find(1,n,1,tree[tree[y].top].To_tree,tree[y].To_tree));
            y=tree[tree[y].top].father; 
        }
    }
    if(x==y) return ans;
    if(tree[x].depth<tree[y].depth) ans=min(ans,find(1,n,1,tree[tree[x].heavy_son].To_tree,tree[y].To_tree));
    else ans=min(ans,find(1,n,1,tree[tree[y].heavy_son].To_tree,tree[x].To_tree));
    return ans;
}
int main()
{
    freopen("truck.in","r",stdin);
    freopen("truck.out","w",stdout);
    scanf("%d%d",&n,&m);
    int i,j,xx,yy;
    for (i=1;i<=n;++i)father[i]=i;
    for (i=1;i<=m;++i)scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
    sort(a+1,a+1+m,cmp);
    for (i=1;i<=m;++i)
    {
        xx=getfather(a[i].x);   
        yy=getfather(a[i].y);
        if(xx!=yy) 
        {
            father[xx]=yy;
            insert(a[i].x,a[i].y,a[i].z);
            insert(a[i].y,a[i].x,a[i].z);
        }
    }
    tree[1].depth=1;
    tot=0;
    for (int i=1;i<=n;i++)
        if (!bz[i])
        {
            tree[i].depth=1;
            tree[i].father=0;
            dfs(i),dfs2(i,i);
        }
    memset(f,127,sizeof(f));
    buildtree(1,n,1);
    scanf("%d",&q);
    for (i=1;i<=q;++i)
    {
        scanf("%d%d",&xx,&yy);
        if(getfather(xx)!=getfather(yy)) printf("-1\n");
        else printf("%lld\n",query_ans(xx,yy));
    }
} 

再推荐几道例题:
JZOJsenior2753.【2012东莞市选】树(tree)
【ZJOI2008】树的统计
树的统计CODE:

#include<cstdio>
#include<algorithm>
#define MAXN 30005
#define INF 1<<30
using namespace std;
int l[MAXN],r[MAXN];
struct node{int sum,max;};
node tree[MAXN*4];     
struct Edge{int v,next;};//v:value
Edge edge[MAXN*2];
int head[MAXN],now,val[MAXN],link[MAXN],dep[MAXN],fa[MAXN],sonTree[MAXN],heavySon[MAXN],tot,top[MAXN],num[MAXN];     
int n,u,v,q;
void addEdge(int u,int v)
{
    now++;
    edge[now].v=v;
    edge[now].next=head[u];
    head[u]=now;
}
void buildTree(int now,int l,int r)
{
    if (l==r) 
    {
        tree[now].max=val[link[l]];//link(position)
        tree[now].sum=val[link[l]];
        return;
    }
    int mid=(l+r)/2;
    buildTree(now*2,l,mid);
    buildTree(now*2+1,mid+1,r);
    tree[now].max=max(tree[now*2].max,tree[now*2+1].max);
    tree[now].sum=tree[now*2].sum+tree[now*2+1].sum;
}

void DFS1(int now,int nowFa,int nowDep)
//HeavySon,Dep,SonTree,Father
{
    dep[now]=nowDep;fa[now]=nowFa;sonTree[now]=1;
    for (int x=head[now];x!=0;x=edge[x].next)
    {
        if(edge[x].v==nowFa) continue;//bz
        DFS1(edge[x].v,now,nowDep+1);
        sonTree[now]+=sonTree[edge[x].v];
        if(heavySon[now]==0||sonTree[edge[x].v]>sonTree[heavySon[now]]) heavySon[now]=edge[x].v;
    }
}
void DFS2(int now,int nowTop)
//num:The numbering after this point is split 
//link:The position of the current node in the segment tree
{
    tot++;
    top[now]=nowTop; num[now]=tot; link[tot]=now;
    if (heavySon[now]==0) return;
    DFS2(heavySon[now],nowTop);
    for (int x=head[now];x!=0;x=edge[x].next)
        if(edge[x].v!=heavySon[now]&&edge[x].v!=fa[now]) DFS2(edge[x].v,edge[x].v);
}

void init()
{
    scanf("%d",&n);
    for (int i=1;i<=n-1;i++) 
    {
        scanf("%d %d",&u,&v);
        addEdge(u,v),addEdge(v,u);
    } 
    for (int i=1;i<=n;i++) scanf("%d",&val[i]);
    DFS1(1,0,1); DFS2(1,1);
    buildTree(1,1,n);
}

void update1(int now,int l,int r,int loc,int delta)
{
    if (l==r)
    {
        tree[now].sum+=delta;
        tree[now].max+=delta;
        return;
    }
    int mid=(l+r)/2;
    if (loc<=mid) update1(now*2,l,mid,loc,delta);
    else update1(now*2+1,mid+1,r,loc,delta);
    tree[now].max=max(tree[now*2].max,tree[now*2+1].max);
    tree[now].sum=tree[now*2].sum+tree[now*2+1].sum;
}

int query1(int now,int l,int r,int ql,int qr)
{
    int ans=0;
    if (ql<=l && r<=qr) return tree[now].sum;
    int mid=(l+r)/2;
    if (ql<=mid) ans+=query1(now*2,l,mid,ql,qr);
    if (qr>mid) ans+=query1(now*2+1,mid+1,r,ql,qr);
    return ans;
}

int query2(int now,int l,int r,int ql,int qr)
{
    int ans=-INF;
    if (ql<=l && r<=qr) return tree[now].max;
    int mid=(l+r)/2;
    if (ql<=mid) ans=max(ans,query2(now*2,l,mid,ql,qr));
    if (qr>mid) ans=max(ans,query2(now*2+1,mid+1,r,ql,qr));
    return ans;
}

int getMax(int l,int r)
{
    int f1=top[l],f2=top[r],ans=-INF,nowAns;
    while (f1!=f2)
    {
        if (dep[f1]<dep[f2]) swap(f1,f2),swap(l,r);
        ans=max(ans,query2(1,1,n,num[f1],num[l]));
        l=fa[f1]; f1=top[l];
    }
    if (dep[l]>dep[r]) nowAns=query2(1,1,n,num[r],num[l]);
    else nowAns=query2(1,1,n,num[l],num[r]);
    ans=max(ans,nowAns);
    return ans;
}

int getSum(int l,int r)
{
    int f1=top[l],f2=top[r],ans=0,nowAns;
    while (f1!=f2)
    {
        if (dep[f1]<dep[f2]) swap(f1,f2),swap(l,r);
        ans+=query1(1,1,n,num[f1],num[l]);
        l=fa[f1]; f1=top[l];
    }
    if (dep[l]>dep[r]) nowAns=query1(1,1,n,num[r],num[l]);
    else nowAns=query1(1,1,n,num[l],num[r]);
    ans+=nowAns;
    return ans;       
}

int main()
{
    char s[7];
    init();
    scanf("%d",&q);
    for (int i=1;i<=q;i++) 
    {
        scanf("%s %d %d",s,&u,&v);
        if (s[0]=='C') update1(1,1,n,num[u],v-val[u]),val[u]=v;//CHANGE
        else if (s[1]=='M') printf("%d\n",getMax(u,v));//QMAX
        else printf("%d\n",getSum(u,v));//QSUM
    }
    return 0;
 }

做完这几道题您将会是树链剖分大佬~

猜你喜欢

转载自blog.csdn.net/cdy1206473601/article/details/79189553
今日推荐