当前位置 博文首页 > 文章内容

    树链剖分

    作者: 栏目:未分类 时间:2020-07-19 14:02:18

    本站于2023年9月4日。收到“大连君*****咨询有限公司”通知
    说我们IIS7站长博客,有一篇博文用了他们的图片。
    要求我们给他们一张图片6000元。要不然法院告我们

    为避免不必要的麻烦,IIS7站长博客,全站内容图片下架、并积极应诉
    博文内容全部不再显示,请需要相关资讯的站长朋友到必应搜索。谢谢!

    另祝:版权碰瓷诈骗团伙,早日弃暗投明。

    相关新闻:借版权之名、行诈骗之实,周某因犯诈骗罪被判处有期徒刑十一年六个月

    叹!百花齐放的时代,渐行渐远!



    树链剖分

    前置芝士

    ​ 就像它的名字,树链剖分是在一棵树上进行,在讲解中还会用到线段树和dfs,如果不会,打开链接自行搜索(主要是线段树的博客没做,还有不要问我为什么这算知识)。

    一个节点的重儿子,为其更大的一颗子树的根节点。从这个点连向重儿子的边我们称为重边

    由重边连续连起来的点和边就组成了重链,也就是树链

    概念

    将一棵树划分成若干条链,用数据结构去维护每条链,复杂度为 \(O(logN)\)

    其实本质是一些数据结构/算法在树上的推广

    作用

    处理树上的一些相关问题。比如——维护树上区间,树上路径等等。

    区间我们想到了线段树,树上路径想到了LCA,但是它们都有一个特点——连续。线段树只能维护连续区间,LCA路径也是不间断的。所以为了便于处理,我们要对这个图重新标号,以便查找。怎么标呢?我们可以想到——在树链上操作LCA路径,那么路径也是要连贯的,也就是说重链上的编号要连贯,所以我们重新编号的时候是在dfs序的基础上遵循先遍历重儿子的原则。

    例题

    洛谷P3384 【模板】树链剖分

    思路

    可以很容易发现——操作1、2都需要走一遍x到y的路径,操作3、4都需要操作以x为根的子树。所以我们先思考怎么遍历这些区间——

    首先遍历x到y的路径,我们亦容易想到LCA——两个点同时往上跳,直到某个值相同,可以一起操作。所以我们的思路就是:两个点不在同一条链就往链头的父亲节点跳,在同一条链上就直接处理。而处理方法也很简单——因为全程都在链上以连续的新节点编号来操作,所以线段树维护区间距离就很方便了,完全不受树剖影响地敲一个基本的建树、查询、区间修改+延迟标记的代码就可以了。

    而对于操作3和4,以x为根的子树,显然编号也是连续的——毕竟编号时的最基本原则还是dfs遍历。但是有一个小问题——我们知道以x为根的子树最小的编号是x的编号,但是最大的编号我们并不知道,如果遍历一遍来找的话复杂度就会比较高了。所以——这是我们在初始化树剖的时候要存储下来的一个变量——以x为根的子树的最大的节点编号。也就是代码中的son[ ]。

    代码
    #include<algorithm>
    #include<iostream>
    #include<cstdlib>
    #include<cstring>
    #include<cstdio>
    #define Rint register int
    #define mem(a,b) memset(a,(b),sizeof(a))
    #define Temp template<typename T>
    using namespace std;
    typedef long long LL;
    Temp inline void read(T &x){
        x=0;T w=1,ch=getchar();
        while(!isdigit(ch)&&ch!='-')ch=getchar();
        if(ch=='-')w=-1,ch=getchar();
        while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^'0'),ch=getchar();
        x=x*w;
    }
    
    #define mid ((l+r)>>1)
    #define lson rt<<1,l,mid
    #define rson rt<<1|1,mid+1,r
    #define len (r-l+1)
    
    const int maxn=200000+10;
    int n,m,r,mod;
    //见题意 
    int e,beg[maxn],nex[maxn],to[maxn],w[maxn],wt[maxn];
    //链式前向星数组,w[]、wt[]初始点权数组 
    int a[maxn<<2],laz[maxn<<2];
    //线段树数组、lazy操作 
    int son[maxn],id[maxn],fa[maxn],cnt,dep[maxn],siz[maxn],top[maxn]; 
    //son[]重儿子编号,id[]新编号,fa[]父亲节点,cnt dfs_cl	ock/dfs序,dep[]深度,siz[]子树大小,top[]当前链顶端节点 
    int res=0;
    //查询答案 
    
    inline void add(int x,int y){//链式前向星加边 
        to[++e]=y;
        nex[e]=beg[x];
        beg[x]=e;
    }
    //-------------------------------------- 以下为线段树 
    inline void pushdown(int rt,int lenn){
        laz[rt<<1]+=laz[rt];
        laz[rt<<1|1]+=laz[rt];
        a[rt<<1]+=laz[rt]*(lenn-(lenn>>1));
        a[rt<<1|1]+=laz[rt]*(lenn>>1);
        a[rt<<1]%=mod;
        a[rt<<1|1]%=mod;
        laz[rt]=0;
    }
    
    inline void build(int rt,int l,int r){
        if(l==r){
            a[rt]=wt[l];
            if(a[rt]>mod)a[rt]%=mod;
            return;
        }
        build(lson);
        build(rson);
        a[rt]=(a[rt<<1]+a[rt<<1|1])%mod;
    }
    
    inline void query(int rt,int l,int r,int L,int R){
        if(L<=l&&r<=R){res+=a[rt];res%=mod;return;}
        else{
            if(laz[rt])pushdown(rt,len);
            if(L<=mid)query(lson,L,R);
            if(R>mid)query(rson,L,R);
        }
    }
    
    inline void update(int rt,int l,int r,int L,int R,int k){
        if(L<=l&&r<=R){
            laz[rt]+=k;
            a[rt]+=k*len;
        }
        else{
            if(laz[rt])pushdown(rt,len);
            if(L<=mid)update(lson,L,R,k);
            if(R>mid)update(rson,L,R,k);
            a[rt]=(a[rt<<1]+a[rt<<1|1])%mod;
        }
    }
    //---------------------------------以上为线段树 
    inline int qRange(int x,int y){
        int ans=0;
        while(top[x]!=top[y]){//当两个点不在同一条链上 
            if(dep[top[x]]<dep[top[y]])swap(x,y);//把x点改为所在链顶端的深度更深的那个点 
            res=0;
            query(1,1,n,id[top[x]],id[x]);//ans加上x点到x所在链顶端 这一段区间的点权和 
            ans+=res;
            ans%=mod;//按题意取模 
            x=fa[top[x]];//把x跳到x所在链顶端的那个点的上面一个点 
        }
        //直到两个点处于一条链上
        if(dep[x]>dep[y])swap(x,y);//把x点改为所在链顶端的深度更浅的那个点 
        res=0;
        query(1,1,n,id[x],id[y]);//这时再加上此时两个点的区间和即可 
        ans+=res;
        return ans%mod;
    }
    
    inline void updRange(int x,int y,int k){//同上 
        k%=mod;
        while(top[x]!=top[y]){
            if(dep[top[x]]<dep[top[y]])swap(x,y);
            update(1,1,n,id[top[x]],id[x],k);
            x=fa[top[x]];
        }
        if(dep[x]>dep[y])swap(x,y);
        update(1,1,n,id[x],id[y],k);
    }
    
    inline int qSon(int x){
        res=0;
        query(1,1,n,id[x],id[x]+siz[x]-1);//子树区间右端点为id[x]+siz[x]-1 
        return res;
    }
    
    inline void updSon(int x,int k){//同上 
        update(1,1,n,id[x],id[x]+siz[x]-1,k);
    }
    
    inline void dfs1(int x,int f,int deep){//x当前节点,f父亲,deep深度 
        dep[x]=deep;//标记每个点的深度 
        fa[x]=f;//标记每个点的父亲 
        siz[x]=1;//标记每个非叶子节点的子树大小 
        int maxson=-1;//记录重儿子的儿子数 
        for(Rint i=beg[x];i;i=nex[i]){
            int y=to[i];
            if(y==f)continue;//若为父亲则continue 
            dfs1(y,x,deep+1);//dfs其儿子 
            siz[x]+=siz[y];//把它的儿子数加到它身上 
            if(siz[y]>maxson)son[x]=y,maxson=siz[y];//标记每个非叶子节点的重儿子编号 
        }
    }
    
    inline void dfs2(int x,int topf){//x当前节点,topf当前链的最顶端的节点 
        id[x]=++cnt;//标记每个点的新编号 
        wt[cnt]=w[x];//把每个点的初始值赋到新编号上来 
        top[x]=topf;//这个点所在链的顶端 
        if(!son[x])return;//如果没有儿子则返回 
        dfs2(son[x],topf);//按先处理重儿子,再处理轻儿子的顺序递归处理 
        for(Rint i=beg[x];i;i=nex[i]){
            int y=to[i];
            if(y==fa[x]||y==son[x])continue;
            dfs2(y,y);//对于每一个轻儿子都有一条从它自己开始的链 
        }
    }
    
    int main(){
        read(n);read(m);read(r);read(mod);
        for(Rint i=1;i<=n;i++)read(w[i]);
        for(Rint i=1;i<n;i++){
            int a,b;
            read(a);read(b);
            add(a,b);add(b,a);
        }
        dfs1(r,0,1);
        dfs2(r,r);
        build(1,1,n);
        while(m--){
            int k,x,y,z;
            read(k);
            if(k==1){
                read(x);read(y);read(z);
                updRange(x,y,z);
            }
            else if(k==2){
                read(x);read(y);
                printf("%d\n",qRange(x,y));
            }
            else if(k==3){
                read(x);read(y);
                updSon(x,y);
            }
            else{
                read(x);
                printf("%d\n",qSon(x));
            }
        }
    }
    

    例题2

    洛谷P1967

    思路

    这是边转点,顾名思义就是把边权转化为点权来用。而又因为每个点的父亲是唯一的,所以我们都是把边权给了深度更大的那个点。

    在这个题里,我们用边转点的树链剖分来求LCA,剩下的和本题题解思路一样,用Kruskal求最大生成树。。。

    代码
    #include<bits/stdc++.h>
    #define maxn 10005
    #define maxm 50005
    using namespace std;
    int read()
    {
        register int x = 0, f = 1, ch = getchar();
        while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
        while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
        return x * f;
    }
     
    int n, m;
    struct node
    {
        int u, v, w;
        bool operator < (const node &x) const
        {
            return x.w < w;
        }
    }g[maxm];
     
    int fa[maxn];
    int get(int x)
    {
        if(fa[x] == x) return x;
        return fa[x] = get(fa[x]);
    }
     
    struct edge
    {
        int to, w, nxt;
        edge(){}
        edge(int tt, int ww, int nn)
        {
            to = tt, w = ww, nxt = nn;
        }
    }e[maxn << 1];
     
    int k = 0, head[maxn];
    void add(int u, int v, int w)
    {
        e[k] = edge(v, w, head[u]);
        head[u] = k++;
    }
     
    int dep[maxn], size[maxn], son[maxn], val[maxn], fa_[maxn];
    void dfs_1(int u)//树剖初始化1
    {
        size[u] = 1;
        for(register int i = head[u]; ~i; i = e[i].nxt)
        {
            register int v = e[i].to, w = e[i].w;
            if(v == fa_[u]) continue;
            dep[v] = dep[u] + 1;
            fa_[v] = u;
            dfs_1(v);
            size[u] += size[v];
            if(size[v] > size[son[u]]) son[u] = v;
        }
    }
     
    int top[maxn], dfn[maxn], tot = 0;
    void dfs_2(int u, int tp)//树剖初始化2
    {
        top[u] = tp;
        dfn[u] = ++tot;
        if(son[u]) dfs_2(son[u], tp);
        for(register int i = head[u]; ~i; i = e[i].nxt)
        {
            register int v = e[i].to, w = e[i].w;
            if(v != fa_[u] && v != son[u]) dfs_2(v, v);
            val[dfn[v]] = w;
        }
    }
     
    int road[maxn << 2];
    void build(int p, int l, int r)//线段树建树
    {
        if(l == r)
        {
            road[p] = val[l];
            return;
        }
        register int mid = l + r >> 1;
        build(p << 1, l, mid);
        build(p << 1 | 1, mid + 1, r);
        road[p] = min(road[p << 1], road[p << 1 | 1]);
    }
     
    int ask(int p, int l, int r, int ls, int rs)//线段树查询
    {
        if(ls <= l && r <= rs) 
            return road[p];
        }
        register int mid = l + r >> 1, ans = 1 << 30;
        if(ls <= mid) ans = min(ans, ask(p << 1, l, mid, ls, rs));
        if(rs > mid) ans = min(ans, ask(p << 1 | 1, mid + 1, r, ls, rs));
        return ans;
    }
     
    void work(int u, int v)//树剖基本操作
    {
        register int ans = 1 << 30;
        register int tmp1 = u, tmp2 = v;
        while(top[u] != top[v])
        {   
            if(dep[top[u]] > dep[top[v]]) swap(u, v);
            ans = min(ans, ask(1, 1, n, dfn[top[v]], dfn[v]));
            v = fa_[top[v]];
        }
        if(dep[u] > dep[v]) swap(u, v);
        ans = min(ans, ask(1, 1, n, dfn[son[u]], dfn[v]));
        printf("%d\n", ans);
    }
     
    int main()
    {
        n = read(), m = read();
        for(register int i = 1; i <= n; i++)
            fa[i] = i;
        for(register int i = 1; i <= m; i++)
            g[i].u = read(), g[i].v = read(), g[i].w = read();
            
        sort(g + 1, g + 1 + m);
        memset(head, -1, sizeof head);
        register int cnt = 0;//最大生成树开始
        for(register int i = 1; i <= m; i++)
        {
            register int u = g[i].u, v = g[i].v, w = g[i].w;
            if(get(u) == get(v)) continue;
            fa[get(u)] = get(v);
            add(u, v, w);
            add(v, u, w);
            cnt++;
            if(cnt == n - 1) break;//省时处理
        }
        
        for(int i = 1; i <= n; i++)//这里一定要开for!!!QAQ
        	if(!size[i]) dfs_1(i);
        	
        for(int i = 1; i <= n; i++)//这里也一定要开for!!!QAQ
        	if(!top[i]) dfs_2(i, i);
        val[1] = 1 << 30;//赋值
        build(1, 1, n);
        
        register int q, u, v;
        q = read();
        while(q--)
        {
            u = read(), v = read();
            if(get(u) != get(v))//判定不连通情况
            {
                puts("-1");continue;
            }
            work(u, v);
        }
    }