zoukankan      html  css  js  c++  java
  • 树链剖分

    树链剖分看起来是个很高级的算法,但实际上很简单。在介绍这个算法之前,要感谢某位大神cgh,是他教了我树链剖分。



    简介

    什么样的题目要用到树链剖分?

    一般是在某些题目中,在一棵树上,求两点的路径上点权(或边权)的最大值(或最小值、和)……
    支持修改权值的操作。(如果不需要改权值可以直接倍增+树上RMQ,见例题三)

    基本思想

    将儿子分为轻儿子、重儿子,将同一条重链上的点集中在一起。用各种数据结构维护(线段树、树状数组、splay)
    以下的讲解使用线段树。

    详细讲解

    一些概念

    1、重儿子和轻儿子

    设x.size表示以x为根的子树的节点个数
    对于每个非叶子节点,size值最大的儿子,就是重儿子。(如果有相同的size,就随便选一个)
    其它的儿子,都是轻儿子

    2、重链

    重儿子及其父亲连起来,就是重链。
    重链
    如图,灰色的点是重儿子,其它是轻儿子。那几条粗的线就是重链。

    一些性质

    1、若x是轻儿子,x.size<=x.father.size2
    2、从根到某一点的路径上,不超过lg(n)条重,不超过lg(n)条轻边。

    重点部分

    我们可以将重链上的点在线段树中放在一起。
    例如上面的这棵树,点在线段树中的顺序是:
    1,3,6,10,8,9,7,2,5,4
    加粗的是在重链上的点。重链上的点,在线段树的顺序中要连在一起。
    按照这个顺序,用线段树维护。

    实现细节

    预处理

    首先,使用O(n)的dfs(dfs1),求出depth(深度),father,size,heavy_son(重儿子)。

    然后,再使用O(n)的dfs(dfs2),求出top、to_tree、to_num
    对于点x,若x为重儿子,top为x所在重链的顶端;若x为轻儿子,top=x(可以将每个轻儿子当成一条重链)。
    求top其实很简单,递归时设一个参数k,表示x.top。
    递归给重儿子时,k不变;
    递归给轻儿子时,k为它本身。
    具体见代码。
    对于点x,to_tree表示x在线段树中的位置。对于线段树中的第i个位置,to_num为它对应树中的位置。

    在线操作

    1、修改:直接通过to_tree在线段树中直接修改。
    2、求LCA:见例题一
    3、询问x到y的最大值(或最小值、和……):见例题二、三

    时间复杂度:O(n+Qlg2n)


    例题一

    描述

    P3379 【模板】最近公共祖先(LCA)

    题目描述

    如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
    输入输出格式

    输入格式:

    第一行包含三个正整数N、M、S,分别表示树的结点个数、询问的个数和树根结点的序号。
    接下来N-1行每行包含两个正整数x、y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树)。
    接下来M行每行包含两个正整数a、b,表示询问a结点和b结点的最近公共祖先。

    输出格式:

    输出包含M行,每行包含一个正整数,依次为每一个询问的结果。

    输入输出样例

    输入样例#1:

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

    输出样例#1:

    4
    4
    1
    4
    4

    说明

    时空限制:1000ms,128M
    数据规模:
    对于30%的数据:N<=10,M<=10
    对于70%的数据:N<=10000,M<=10000
    对于100%的数据:N<=500000,M<=500000
    样例说明:
    该树结构如下:
    插图
    第一次询问:2、4的最近公共祖先,故为4。
    第二次询问:3、2的最近公共祖先,故为4。
    第三次询问:3、5的最近公共祖先,故为1。
    第四次询问:1、2的最近公共祖先,故为4。
    第五次询问:4、5的最近公共祖先,故为4。
    故输出依次为4、4、1、4、4。

    这是一道LCA的模板题,我曾写过倍增(在线)求LCAST(RMQ)算法(在线)求LCA
    现在我们考虑用树链剖分求LCA(x,y)。
    x.topy.top
    1、选取一个top较深的点k。
    2、k跳到k.top.father。
    这样循环下去,depth较小的点即LCA。

    思考:为什么这么做?
    1、当x.top=y.top时,x和y一定在同一条重链上,那么深度较小的就是LCA。
    2、为什么要选top较深的点而不是它自己较深的点?因为如果选自己较深的那个点,有可能会超过LCA。
    3、为什么跳到k.top.father而不是k.top?因为跳到k.top并不会另一个在同一重链上,跳到k.top.father说明k进入了另一条重链。
    如果还是不懂,可以仔细思考一下。

    因为这题没有点权、边权,可以不用打线段树。

    代码

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    int n,m,s;
    struct EDGE
    {
        int to;
        EDGE* las;
    } e[1000000];//前向星存边
    EDGE* last[500001];
    
    struct Information
    {
        int depth,father,size,heavy_son,top;
    } d[500001];
    void dfs1(int,int,int);//求depth,father,size,heavy_son
    void dfs2(int,int);//求top
    
    int LCA(int,int);
    int main()
    {
        scanf("%d%d%d",&n,&m,&s);
        int i,j=-1,x,y;
        for (i=1;i<n;++i)
        {
            scanf("%d%d",&x,&y);
            e[++j]={y,last[x]};
            last[x]=e+j;
            e[++j]={x,last[y]};
            last[y]=e+j;
        }
    
        dfs1(0,s,0);
        dfs2(s,s);
    
        while (m--)
        {
            scanf("%d%d",&x,&y);
            printf("%d
    ",LCA(x,y));
        }
    }
    
    void dfs1(int fa,int x,int de)
    {
        d[x]={de,fa,1,0};
        EDGE* ei;
        for (ei=last[x];ei;ei=ei->las)
            if (ei->to!=fa)
            {
                dfs1(x,ei->to,de+1);
                d[x].size+=d[ei->to].size;
                if (d[d[x].heavy_son].size<d[ei->to].size)
                    d[x].heavy_son=ei->to;
            }
    }
    void dfs2(int x,int k)
    {
        d[x].top=k;
        if (d[x].heavy_son)
            dfs2(d[x].heavy_son,k);//将k带给重儿子。因为同一重链上的top值相同
        EDGE* ei;
        for (ei=last[x];ei;ei=ei->las)
            if (ei->to!=d[x].father && ei->to!=d[x].heavy_son)
                dfs2(ei->to,ei->to);//轻儿子的top值为它本身
    }
    
    int LCA(int x,int y)
    {
        for (;d[x].top!=d[y].top;)
        {
            if (d[d[x].top].depth>d[d[y].top].depth)
                x=d[d[x].top].father;
            else 
                y=d[d[y].top].father;
        }
        return d[x].depth<d[y].depth?x:y;
    }

    (未完成)


    例题二

    描述

    P2590 [ZJOI2008]树的统计

    题目描述

    一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w。
    我们将以下面的形式来要求你对这棵树完成一些操作:
    I. CHANGE u t : 把结点u的权值改为t
    II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值
    III. QSUM u v: 询问从点u到点v的路径上的节点的权值和
    注意:从点u到点v的路径上的节点包括u和v本身

    输入格式:

    输入文件的第一行为一个整数n,表示节点的个数。
    接下来n – 1行,每行2个整数a和b,表示节点a和节点b之间有一条边相连。
    接下来一行n个整数,第i个整数wi表示节点i的权值。
    接下来1行,为一个整数q,表示操作的总数。
    接下来q行,每行一个操作,以“CHANGE u t”或者“QMAX u v”或者“QSUM u v”的形式给出。

    输出格式:

    对于每个“QMAX”或者“QSUM”的操作,每行输出一个整数表示要求输出的结果。

    输入输出样例

    输入样例#1:

    4
    1 2
    2 3
    4 1
    4 2 1 3
    12
    QMAX 3 4
    QMAX 3 3
    QMAX 3 2
    QMAX 2 3
    QSUM 3 4
    QSUM 2 1
    CHANGE 1 5
    QMAX 3 4
    CHANGE 3 6
    QMAX 3 4
    QMAX 2 4
    QSUM 3 4

    输出样例#1:

    4
    1
    2
    2
    10
    6
    5
    6
    5
    16

    说明

    对于100%的数据,保证1<=n<=30000,0<=q<=200000;中途操作中保证每个节点的权值w在-30000到30000之间。

    分析

    这是树链剖分的经典例题。这题要求两点之间的和或最大值。
    怎么做?
    观察一下例题一种求LCA的过程,可以发现两个要求的答案其实很相似。
    求LCA时,我们要一条重链一条重链地跳上去。可以类似地,在每次跳上去的时候用线段树求出这一段的和或最大值。
    如果还不懂,那可以套上刚才那流程:
    x.topy.top
    1、选取一个top较深的点k。
    2、线段树求出k.top到k这条链上的和或最大值,更新答案。k跳到k.top.father。
    这样循环下去。
    设最后depth较小的是a,另一个是b,线段树求出a到b的和或最大值,更新答案。
    这便是最后的答案。

    仔细想想,其实也显然。每次次从k跳到k.top.father时,用k.top到k之间的和或最大值更新答案。
    这样子就可以做到不重复(因为在计算过程中,x和y一定是没有被算过的)。
    最后x和y在同一条链上,它们之间(包括自己)都没有被算过,所以可以直接用线段树来求。

    代码

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    int n;
    struct EDGE
    {
        int to;
        EDGE* las;
    } e[60000];//前向星存边
    EDGE* last[30001];
    
    struct Information
    {
        int depth,father,size,heavy_son,top,to_tree,value;//to_tree表示它在线段树中的编号
    } d[30001];
    void dfs1(int,int,int);
    int to_num[30001],total;//to_num为从线段树中返回原来的编号
    void dfs2(int,int);
    
    int a[30001];
    struct Node
    {
        int MAX,SUM;
    } tree[120001];
    void build(int,int,int);//建树
    void change(int,int,int,int,int);//修改
    inline int query_max(int,int);//询问最大值
    int Tree_query_max(int,int,int,int,int);//线段树中询问最大值
    inline int query_sum(int,int);//询问和
    int Tree_query_sum(int,int,int,int,int);//线段树中询问和
    
    int main()
    {
        scanf("%d",&n);
        int i,j=-1,x,y;
        for (i=1;i<n;++i)
        {
            scanf("%d %d",&x,&y);
            e[++j]={y,last[x]};
            last[x]=e+j;
            e[++j]={x,last[y]};
            last[y]=e+j;
        }
    
        dfs1(0,1,0);
        dfs2(1,1);
    
        for (i=1;i<=n;++i)
            scanf("%d",&d[i].value);
        build(1,1,n);
    
        int Q;
        scanf("%d",&Q);
        char mode[7];
        while (Q--)
        {
            scanf("%s %d %d",mode,&x,&y);
            switch (mode[1])
            {
                case 'H':
                    change(1,1,n,d[x].to_tree,y);
                break;
                case 'M':
                    printf("%d
    ",query_max(x,y));
                break;
                default:
                    printf("%d
    ",query_sum(x,y));
            }
        }
    } 
    
    void dfs1(int fa,int x,int de) 
    {
        d[x]={de,fa,1};
        EDGE* ei;
        for (ei=last[x];ei;ei=ei->las)
            if (ei->to!=fa)
            {
                dfs1(x,ei->to,de+1);
                d[x].size+=d[ei->to].size;
                if (d[d[x].heavy_son].size<d[ei->to].size)
                    d[x].heavy_son=ei->to;
            }
    }
    void dfs2(int x,int k)
    {
        d[x].top=k;
        d[x].to_tree=++total;//对其进行编号。这里有个显然的性质:爸爸的在线段树中编号一定在儿子的前面
        to_num[total]=x;
        if (d[x].heavy_son)
            dfs2(d[x].heavy_son,k);
        EDGE* ei;
        for (ei=last[x];ei;ei=ei->las)
            if (ei->to!=d[x].father && ei->to!=d[x].heavy_son)
                dfs2(ei->to,ei->to);
    }
    
    void build(int k,int l,int r)
    {
        if (l==r)
        {
            tree[k]={d[to_num[l]].value,d[to_num[l]].value};
            return;
        }
        int mid=(l+r)>>1;
        build(k<<1,l,mid);
        build(k<<1|1,mid+1,r);
        tree[k]={max(tree[k<<1].MAX,tree[k<<1|1].MAX),tree[k<<1].SUM+tree[k<<1|1].SUM};
    }
    void change(int k,int l,int r,int x,int y)
    {
        if (l==r)
        {
            tree[k]={y,y};
            return;
        }
        int mid=(l+r)>>1;
        if (x<=mid)
            change(k<<1,l,mid,x,y);
        else
            change(k<<1|1,mid+1,r,x,y);
        tree[k]={max(tree[k<<1].MAX,tree[k<<1|1].MAX),tree[k<<1].SUM+tree[k<<1|1].SUM};
    }
    
    inline int query_max(int x,int y)
    {
        int ret=-2147483648;
        for (;d[x].top!=d[y].top;)
        {
            if (d[d[x].top].depth>d[d[y].top].depth)
            {
                ret=max(ret,Tree_query_max(1,1,n,d[d[x].top].to_tree,d[x].to_tree));//计算x.top到x之间的最大值 在线段树中,x.top的编号一定在x钱
                x=d[d[x].top].father;
            }
            else 
            {
                ret=max(ret,Tree_query_max(1,1,n,d[d[y].top].to_tree,d[y].to_tree));
                y=d[d[y].top].father;
            }
        }
        if (d[x].depth<d[y].depth)
            ret=max(ret,Tree_query_max(1,1,n,d[x].to_tree,d[y].to_tree));//计算x到y的最大值
        else
            ret=max(ret,Tree_query_max(1,1,n,d[y].to_tree,d[x].to_tree));
        return ret;
    }
    int Tree_query_max(int k,int l,int r,int st,int en)
    {
        if (l==st && r==en)
            return tree[k].MAX;
        int mid=(l+r)>>1;
        if (en<=mid)
            return Tree_query_max(k<<1,l,mid,st,en);
        else if (mid<st)
            return Tree_query_max(k<<1|1,mid+1,r,st,en);
        else
            return max(Tree_query_max(k<<1,l,mid,st,mid),Tree_query_max(k<<1|1,mid+1,r,mid+1,en));
    }
    inline int query_sum(int x,int y)
    {
        int ret=0;
        for (;d[x].top!=d[y].top;)
        {
            if (d[d[x].top].depth>d[d[y].top].depth)
            {
                ret+=Tree_query_sum(1,1,n,d[d[x].top].to_tree,d[x].to_tree);
                x=d[d[x].top].father;
            }
            else 
            {
                ret+=Tree_query_sum(1,1,n,d[d[y].top].to_tree,d[y].to_tree);
                y=d[d[y].top].father;
            }
        }
        if (d[x].depth<d[y].depth)
            ret+=Tree_query_sum(1,1,n,d[x].to_tree,d[y].to_tree);
        else
            ret+=Tree_query_sum(1,1,n,d[y].to_tree,d[x].to_tree);
        return ret;
    }
    int Tree_query_sum(int k,int l,int r,int st,int en)
    {
        if (l==st && r==en)
            return tree[k].SUM;
        int mid=(l+r)>>1;
        if (en<=mid)
            return Tree_query_sum(k<<1,l,mid,st,en);
        else if (mid<st)
            return Tree_query_sum(k<<1|1,mid+1,r,st,en);
        else
            return Tree_query_sum(k<<1,l,mid,st,mid)+Tree_query_sum(k<<1|1,mid+1,r,mid+1,en);
    }

    例题三

    描述

    Heatwave

    Description

      给你N个点的无向连通图,图中有M条边,第j条边的长度为: d_j.
      现在有 K个询问。
      每个询问的格式是:A B,表示询问从A点走到B点的所有路径中,最长的边最小值是多少?

    Input

      文件名为heatwave.in
      第一行: N, M, K。
      第2..M+1行: 三个正整数:X, Y, and D (1 <= X <=N; 1 <= Y <= N). 表示X与Y之间有一条长度为D的边。
      第M+2..M+K+1行: 每行两个整数A B,表示询问从A点走到B点的所有路径中,最长的边最小值是多少?

    Output

      对每个询问,输出最长的边最小值是多少。

    Sample Input

    6 6 8
    1 2 5
    2 3 4
    3 4 3
    1 4 8
    2 5 7
    4 6 2
    1 2
    1 3
    1 4
    2 3
    2 4
    5 1
    6 2
    6 1

    Sample Output

    5
    5
    5
    4
    4
    7
    4
    5

    Data Constraint

    50% 1<=N,M<=3000 其中30% K<=5000
    ​100% 1 <= N <= 15,000   1 <= M <= 30,000   1 <= d_j <= 1,000,000,000   1 <= K <= 20,000

    这题并不是个裸题。先说一下解法。
    这题要求所有路径中的最长边的最小值。想让答案最小,显然可以用最小生成树。
    为什么最小生成树是对的?
    其实很显然。假设有两部分点,每一部分的点可以互相到达连起来,
    那么要连接两个区域,就必须走过连同两个区域中的点的一条边。
    这条边当然要越小越好。
    一开始的时候,每一个点都是独立的,最小生成树当然是最好的选择。

    由于这题不需要修改,所以正解是最小生成树+树上RMQ(倍增)。
    树上RMQ类似于且需要倍增(不会倍增的点这里),具体见代码。
    放上树上RMQ(倍增)代码

    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    using namespace std;
    int n,m;
    
    struct EDGE_For_MST
    {
        int x,y,len;
    } em[30001];
    bool cmp(const EDGE_For_MST& a,const EDGE_For_MST& b){return a.len<b.len;}
    int father[15001];
    int getfather(int x)
    {
        if (father[x]==x)
            return x;
        return father[x]=getfather(father[x]);
    }
    
    struct EDGE
    {
        int to,len;
        EDGE* las;
    } e[30001];
    EDGE* last[15001];
    
    struct Information
    {
        int depth;
        int fa[14],MAX[14];
    } f[15001];
    void dfs(int,int,int,int);
    
    inline int query(int x,int y);
    int main()
    {
        int Q;
        scanf("%d%d%d",&n,&m,&Q);
        int i;
        for (i=1;i<=m;++i)
            scanf("%d%d%d",&em[i].x,&em[i].y,&em[i].len);
        //最小生成树
        sort(em+1,em+m+1,cmp);
        for (i=1;i<=n;++i)
            father[i]=i;
        int j=0,k=0,xx,yy;
        for (i=1;i<=m;++i)
        {
            xx=getfather(em[i].x);
            yy=getfather(em[i].y);
            if (xx!=yy)
            {
                father[xx]=yy;
                e[++k]={em[i].y,em[i].len,last[em[i].x]};
                last[em[i].x]=e+k;
                e[++k]={em[i].x,em[i].len,last[em[i].y]};
                last[em[i].y]=e+k;
                if (++j==n-1)
                    break;
            }
        }
        //树上RMQ求答案
        dfs(0,0,1,0);
        int ans;
        for (;Q;--Q)
        {
            scanf("%d%d",&xx,&yy);
            printf("%d
    ",query(xx,yy));
        }
    } 
    void dfs(int len,int father,int x,int de)
    {
        f[x].depth=de;
        f[x].fa[0]=father;
        f[x].MAX[0]=len;
        int i,j;
        for (i=1,j=2;j<=de;++i,j<<=1)
        {
            f[x].fa[i]=f[f[x].fa[i-1]].fa[i-1];
            f[x].MAX[i]=max(f[x].MAX[i-1],f[f[x].fa[i-1]].MAX[i-1]);
        }
        EDGE* ei;
        for (ei=last[x];ei;ei=ei->las)
            if (father!=ei->to)
                dfs(ei->len,x,ei->to,de+1);
    }
    inline int query(int x,int y)
    {
        if (f[x].depth<f[y].depth)
            swap(x,y);
        int ret=0,i=0,k=f[x].depth-f[y].depth;
        while (k)
        {
            if (k&1)
            {
                ret=max(ret,f[x].MAX[i]);
                x=f[x].fa[i];
            }
            k>>=1;
            ++i;
        }
        if (x==y)
            return ret;
        for (i=int(log2(f[x].depth));i>=0;--i)
            if (f[x].fa[i]!=f[y].fa[i])
            {
                ret=max({ret,f[x].MAX[i],f[y].MAX[i]});
                x=f[x].fa[i];
                y=f[y].fa[i];
            }
        return max({ret,f[x].MAX[0],f[y].MAX[0]});
    }

    其实这题可以用树链剖分很好地解决。
    最小生成树后,就和例题二差不多了。只是例题二求点权的和或最大值,这题求边权的和或最大值。
    其实把点转换成边很简单。将每条边的权值放在与此边连的儿子上,就能成功将边权转换成点权。
    求的时候跟例题二一样,只是不需要算LCA(x,y)的点权罢了。

    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    using namespace std;
    int n,m;
    
    struct EDGE_For_MST
    {
        int x,y,len;
    } em[30001];
    bool cmp(const EDGE_For_MST& a,const EDGE_For_MST& b){return a.len<b.len;}
    int father[15001];
    int getfather(int x)
    {
        if (father[x]==x)
            return x;
        return father[x]=getfather(father[x]);
    }
    
    struct EDGE
    {
        int to,len;
        EDGE* las;
    } e[30001];
    EDGE* last[15001];
    
    struct Information
    {
        int depth,father,size,heavy_son,top,to_tree,value;
    } d[15001];
    void dfs1(int,int,int,int);
    int to_num[15001],total;
    void dfs2(int,int);
    
    int tree[60001];
    void build(int,int,int);
    
    inline int query(int,int);
    int Tree_query(int,int,int,int,int);
    int main()
    {
        int Q;
        scanf("%d%d%d",&n,&m,&Q);
        int i;
        for (i=1;i<=m;++i)
            scanf("%d%d%d",&em[i].x,&em[i].y,&em[i].len);
        //最小生成树
        sort(em+1,em+m+1,cmp);
        for (i=1;i<=n;++i)
            father[i]=i;
        int j=0,k=0,xx,yy;
        for (i=1;i<=m;++i)
        {
            xx=getfather(em[i].x);
            yy=getfather(em[i].y);
            if (xx!=yy)
            {
                father[xx]=yy;
                e[++k]={em[i].y,em[i].len,last[em[i].x]};
                last[em[i].x]=e+k;
                e[++k]={em[i].x,em[i].len,last[em[i].y]};
                last[em[i].y]=e+k;
                if (++j==n-1)
                    break;
            }
        }
        //树链剖分
        dfs1(0,0,1,0);
        dfs2(1,1);
        build(1,1,n);
    
        while (Q--)
        {
            scanf("%d%d",&xx,&yy);
            printf("%d
    ",query(xx,yy));    
        } 
    } 
    
    void dfs1(int le,int fa,int x,int de)
    {
        d[x]={de,fa,1,0,0,0,le};
        EDGE* ei;
        for (ei=last[x];ei;ei=ei->las)
            if (ei->to!=fa)
            {
                dfs1(ei->len,x,ei->to,de+1);
                d[x].size+=d[ei->to].size;
                if (d[d[x].heavy_son].size<d[ei->to].size)
                    d[x].heavy_son=ei->to;
            }
    }
    void dfs2(int x,int k)
    {
        d[x].top=k;
        d[x].to_tree=++total;
        to_num[total]=x;
        if (d[x].heavy_son)
            dfs2(d[x].heavy_son,k);
        EDGE* ei;
        for (ei=last[x];ei;ei=ei->las)
            if (ei->to!=d[x].father && ei->to!=d[x].heavy_son)
                dfs2(ei->to,ei->to);
    }
    void build(int k,int l,int r)
    {
        if (l==r)
        {
            tree[k]=d[to_num[l]].value;
            return;
        }
        int mid=(l+r)>>1;
        build(k<<1,l,mid);
        build(k<<1|1,mid+1,r);
        tree[k]=max(tree[k<<1],tree[k<<1|1]);
    }
    
    inline int query(int x,int y)
    {
        int ret=0;
        for (;d[x].top!=d[y].top;)
            if (d[d[x].top].depth>d[d[y].top].depth)
            {
                ret=max(ret,Tree_query(1,1,n,d[d[x].top].to_tree,d[x].to_tree));
                x=d[d[x].top].father;
            }
            else
            {
                ret=max(ret,Tree_query(1,1,n,d[d[y].top].to_tree,d[y].to_tree));
                y=d[d[y].top].father;
            }
        if (x==y)//若x==y,那么线段树询问时l>r,会RE
            return ret;
        if (d[x].depth<d[y].depth)
            ret=max(ret,Tree_query(1,1,n,d[d[x].heavy_son].to_tree,d[y].to_tree));
        else
            ret=max(ret,Tree_query(1,1,n,d[d[y].heavy_son].to_tree,d[x].to_tree));
        return ret;
    }
    int Tree_query(int k,int l,int r,int st,int en)
    {
        if (l==st && r==en)
            return tree[k];
        int mid=(l+r)>>1;
        if (en<=mid)
            return Tree_query(k<<1,l,mid,st,en);
        else if (mid<st)    
            return Tree_query(k<<1|1,mid+1,r,st,en);
        else
            return max(Tree_query(k<<1,l,mid,st,mid),Tree_query(k<<1|1,mid+1,r,mid+1,en));
    }

    总结

    1、 当遇到求树上两点间的路径上,点权或边权的和(最大值、最小值……)的题目时,可以用树链剖分。
    2、 树链剖分支持修改点权、边权的操作,但不支持删边、删点操作(要这么做就得用LCT)
    3、 树链剖分就是将点分成几部分,同是重链的点在线段树(或其它数据结构)中的编号连在一起,用线段树(或其它数据结构维护)。

  • 相关阅读:
    SharePoint 2013 APP 开发示例 (六)服务端跨域访问 Web Service (REST API)
    麦咖啡导致电脑不能上网
    SharePoint 2013 Central Admin 不能打开
    SharePoint 2013 APP 开发示例 (五)跨域访问 Web Service (REST API)
    SharePoint 2013 APP 开发示例 系列
    synthesize(合成) keyword in IOS
    Git Cmd
    简单的正则匹配
    Dropbox
    SQL Server Replication
  • 原文地址:https://www.cnblogs.com/jz-597/p/11145306.html
Copyright © 2011-2022 走看看