树的重链剖分

本文介绍了如何利用深度优先搜索的性质进行树的重链剖分,包括子树区间修改和从节点x到y的最短路径查询优化。重点讲解了重节点和轻节点的概念,以及如何通过记录重链时间戳连续性来实现高效查询和修改操作,最后给出了两个实际问题的模板代码示例:重链剖分和最近公共祖先计算。
摘要由CSDN通过智能技术生成

前排感谢AgOH大佬 orz orz :【AgOHの算法胡扯】dfs序与树链剖分_哔哩哔哩_bilibili

树的重链剖分本质上就是利用dfs获得树的一个具有较优性质的线性序列。

首先我们知道dfs(或者说树先序遍历)的四条性质

1.祖先的后代性:任何非树边(变成树之后多出的部分)的两个端点具有祖先后代关系

2.子树的独立性:节点的每个儿子(直接儿子)的子树之间没有边,这是DFS保证的(类似于染色)

3.时间戳的区间性:子树的时间戳为一段区间(连续的一段区间)

4.时间戳的单调性:节点的时间戳小于子树内节点的时间戳

(时间戳指节点的访问顺序。)

看题目:P3384 【模板】重链剖分/树链剖分 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

首先是子树的区间修改,这个问题其实不需要重链剖分,这个很简单:

假设节点x时间戳为t,以x为根的子树节点个数为node_count,由于时间戳的区间性和时间戳的单调性,我们知道这个子树的时间戳范围为 [t,t+node_count-1],这样我们可以直接利用线段树或者树状数组之类的数据结构进行区间查询和区间修改。

难点在于从节点x到节点y的最短路径这条链上的查询和修改,如果暴力,显然时间复杂度是O(n),因为题目所给树是一个链是一个很方便的卡时间方法。

下面就是重链剖分

1.一个节点不是重节点就是轻节点

2.根节点为轻节点

3.一个节点x如果是重节点 等价于 节点x是所有兄弟节点中子树节点个数最多的那一个。否则为轻节点。

4.以轻节点为头,优先沿着重节点一路向下,会得到一个时间戳连续的链,称这个链为重链。

根据上面四个性质,我们显然可以发现

1.一个节点只要有儿子,就一定有重儿子

2.一个节点是轻节点那么他一定是一个重链的开始

3.一般来说重节点多而轻节点少

利用重链时间戳的连续性,可以记录下 top[x] 表示一个节点所在重链的起点,然后快速跳跃,实现快速的查询和修改:时间复杂度:O(logn)具体证明可搜索。

下面是代码,交上去即可AC。

洛谷P3384 AC代码:

#include <iostream>
#include <cmath>
#include <ctime>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>

#define ll long long

using namespace std;

const int maxn=1e5+10;

vector<int> graph[maxn];//邻接表

struct NODE{
    int fa,son,node_count,depth;
    int dfn,top,w;
}node[maxn];

//first是和,second是lazy tag
pair<int,int> tree[maxn<<2];

int n,m,root,mod,tim,weight[maxn];

//第一次填上 depth,fa和node_count并找到重儿子
void dfs1(int now,int fa)
{
    node[now].fa=fa;
    node[now].depth=node[fa].depth+1;
    node[now].node_count=1;

    int maxsize=-1;

    for(const auto&it:graph[now])
    {
        if(it==fa)continue;
        dfs1(it,now);

        node[now].node_count+=node[it].node_count;

        if(node[it].node_count>maxsize)
        {
            maxsize=node[it].node_count;
            node[now].son=it;
        }
    }
}

//t表示top,
void dfs2(int now,int t)
{
    node[now].dfn=++tim;
    node[now].top=t;
    weight[tim]=node[now].w;

    //如果没有重儿子,说明到叶子结点了,返回
    if(node[now].son==0)return;

    //先走重儿子
    dfs2(node[now].son,t);

    //下面走轻儿子 
    for(const auto&it:graph[now])
    {
        if(it==node[now].son||
            it==node[now].fa)
            continue;
        
        //路线的第一个为轻儿子
        dfs2(it,it);
    }
}

void pushup(pair<int,int>&x,pair<int,int>&l,pair<int,int>&r)
{
    x.first=(l.first+r.first)%mod;
}

void pushdown(int now,int l,int r)
{
    int mid=l+r>>1;

    tree[now<<1].second+=tree[now].second;
    tree[now<<1|1].second+=tree[now].second;

    tree[now<<1].first+=(mid-l+1)*tree[now].second%mod;
    tree[now<<1|1].first+=(r-mid)*tree[now].second%mod;

    tree[now].second=0;
}

void build(int now,int l,int r)
{
    tree[now].second=0;
    if(l==r)
    {
        tree[now].first=weight[l];
        return;
    }

    int mid=l+r>>1;
    build(now<<1,l,mid);
    build(now<<1|1,mid+1,r);
    pushup(tree[now],tree[now<<1],tree[now<<1|1]);
}

int query(int now,int l,int r,int L,int R)
{
    if(l>=L&&r<=R)
        return tree[now].first;

    int mid=l+r>>1,ans=0;

    pushdown(now,l,r);

    if(mid>=L)
        ans=(ans+query(now<<1,l,mid,L,R))%mod;
    
    if(mid<R)
        ans=(ans+query(now<<1|1,mid+1,r,L,R))%mod;
    
    return ans;
}

void modify(int now,int l,int r,int L,int R,int k)
{
    if(l>=L&&r<=R)
    {
        tree[now].first+=(r-l+1)*k;
        tree[now].second+=k;
        return;
    }

    int mid=l+r>>1;
    pushdown(now,l,r);

    if(mid>=L)
        modify(now<<1,l,mid,L,R,k);
    if(mid<R)
        modify(now<<1|1,mid+1,r,L,R,k);

    pushup(tree[now],tree[now<<1],tree[now<<1|1]);
}

void modifytree(int now,int k)
{
    modify(1,1,n,node[now].dfn,
        node[now].dfn+node[now].node_count-1,k);
}

int querytree(int now)
{
    return query(1,1,n,node[now].dfn,
        node[now].dfn+node[now].node_count-1);
}

void modifychain(int x,int y,int k)
{
    k%=mod;

    while(node[x].top!=node[y].top)
    {
        if(node[node[x].top].depth<
            node[node[y].top].depth)
            swap(x,y);
        
        //这里是 dfn[top[x]] ,弄错了一次,怎么找都没找到。一直错。
        modify(1,1,n,node[node[x].top].dfn,node[x].dfn,k);
        x=node[node[x].top].fa;
    }

    if(node[x].dfn>node[y].dfn)
        swap(x,y);
    
    modify(1,1,n,node[x].dfn,node[y].dfn,k);
}

int querychain(int x,int y)
{
    int ans=0;

    while(node[x].top!=node[y].top)
    {
        if(node[node[x].top].depth<
            node[node[y].top].depth)
            swap(x,y);
        
        ans=(ans+query(1,1,n,node[node[x].top].dfn,node[x].dfn))%mod;
        x=node[node[x].top].fa;
    }

    if(node[x].dfn>node[y].dfn)
        swap(x,y);
    
    ans=(ans+query(1,1,n,node[x].dfn,node[y].dfn))%mod;
    return ans;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);

#ifdef LOCAL
    clock_t c1 = clock();
    freopen("in.in","r",stdin);
    freopen("out.out","w",stdout);
#endif

//-------------------------------------------------

    cin>>n>>m>>root>>mod;

    for(int i=1;i<=n;++i)
        cin>>node[i].w;
    

    for(int i=1,x,y;i<n;++i)
    {
        cin>>x>>y;
        graph[x].push_back(y);
        graph[y].push_back(x);
    }

    dfs1(root,root);
    dfs2(root,root);

    build(1,1,n);

    int x,y,z,op;

    while(m--)
    {
        cin>>op>>x;

        if(op==4)
        {
            cout<<querytree(x)%mod<<'\n';
            continue;
        }
        cin>>y;

        if(op==3)
        {
            modifytree(x,y);
            continue;
        }
        else if(op==2)
        {
            cout<<querychain(x,y)%mod<<'\n';
            continue;
        }

        cin>>z;

        modifychain(x,y,z);
    }

//-------------------------------------------------

#ifdef LOCAL
    cout << "Time Used:\n" << clock() - c1 << " ms" << endl;
#endif

    //system("pause");
    return 0;
}

课后小作业:P3379 【模板】最近公共祖先(LCA) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

代码:

#include <iostream>
#include <cmath>
#include <ctime>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>

#define ll long long

using namespace std;

const int maxn=500000+10;

vector<int> graph[maxn];
int n,m,root;
int top[maxn],fa[maxn],son[maxn],node_count[maxn],depth[maxn];

void dfs1(int now,int f)
{
    depth[now]=depth[f]+1;
    fa[now]=f;
    node_count[now]=1;

    int maxsize=-1;

    for(const auto&it : graph[now])
    {
        if(it==f)continue;
        dfs1(it,now);

        node_count[now]+=node_count[it];

        if(node_count[it]>maxsize)
        {
            son[now]=it;
            maxsize=node_count[it];
        }
    }
}

void dfs2(int now,int t)
{
    top[now]=t;

    if(son[now]==0)
        return;

    dfs2(son[now],t);

    for(const auto&it : graph[now])
    {
        if(it==fa[now]||it==son[now])
            continue;
        
        dfs2(it,it);
    }
}

int lca(int x,int y)
{
    while(top[x]!=top[y])
    {
        if(depth[top[x]]<depth[top[y]])
            swap(x,y);
        
        x=fa[top[x]];
    }

    return depth[x]<depth[y]?x:y;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);

#ifdef LOCAL
    clock_t c1 = clock();
    freopen("in.in","r",stdin);
    freopen("out.out","w",stdout);
#endif

//-------------------------------------------------

    cin>>n>>m>>root;

    for(int i=1,x,y;i<n;++i)
    {
        cin>>x>>y;
        graph[x].push_back(y);
        graph[y].push_back(x);
    }
    
    dfs1(root,root);
    dfs2(root,root);
    int x,y;
    while(m--)
    {
        cin>>x>>y;
        cout<<lca(x,y)<<'\n';
    }

//-------------------------------------------------

#ifdef LOCAL
    cout << "Time Used: " << clock() - c1 << " ms" << endl;
#endif

    //system("pause");
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DogDu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值