树链剖分基本原理

树链剖分本质上就是将一棵树拆分为多条树链,并按照一定的规则进行有规律的存储,简化我们树上的操作。

 

几个定义:

size(k) : 以k为根节点时,k的子树的节点个数。

重儿子:u的子节点中size()最大的点

轻儿子:除了重儿子,剩余的节点都为轻儿子

重边:若根节点为u,u的重儿子为v,则(u,v)为重边

轻边:除了重边,剩余的边都为轻边

重链:链上的所有边都为重边

 

需要计算的值:

fa[u]:u的父亲

dep[u]:u节点的深度

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

son[u]:u的重儿子

top[u]:u所在的重链的顶点

tid[u]:剖分后节点的新id

rk[u]:新节点id对应在原树中的节点

 

DFS1 : 计算size,dep,fa,son数组

void DFS1(int now, int from, int deep)   // 寻找重边
{
    dep[now] = deep;
    fa[now] = from;
    siz[now] = 1;
    int length = es[now].size();
    for(int i = 0 ; i < length ; i++)
    {
        int v = es[now][i];
        if(v != from)
        {
            DFS1(v, now, deep + 1);
            siz[now] += siz[v];
            if(son[now] == -1 || siz[v] > siz[son[now]])
            {
                son[now] = v;
            }
        }
    }
}

 

DFS2 :将重边链接成重链(一个点也算重链)

void DFS2(int now, int tp)  // 重边链接成重链
{
    top[now] = tp;
    tid[now] = ++tim; // now 在 线段树 上的编号
    rk[tid[now]] = now;
    if(son[now] == -1) return;
    DFS2(son[now],tp);
    int length = es[now].size();
    for(int i = 0 ; i < length ; i++)
    {
        int v = es[now][i];
        if(v != son[now] && v != fa[now]) DFS2(v,v);
    }
}

得到了以上的结果之后,对于树上路径的操作就变得很容易了,其实树上路径修改就是不断找LCA的过程,如果两点的top相等说明两点在同一条重链上,直接更新区间 【min(tid[y], tid[x]),max(tid[y], tid[x])】 , 如果不在同一条重链上,那么他们的LCA一定不会出现在top较大的重链上,那么不断更新【tid[top[now]] ,tid[now]】,之到当前两点的top相等

void Change(int x, int y, int val)
{
    while(top[x] != top[y])
    {
        if(dep[top[x]] < dep[top[y]]) swap(x,y);
        Update(tid[top[x]], tid[x], val, 1, n, 1);
        x = fa[top[x]];
    }
    if(dep[x] < dep[y]) swap(x,y);
    Update(tid[y], tid[x], val, 1, n, 1);
}

以HDU3966【书上路径整体更新 + 单点查询】为例

 

#include <bits/stdc++.h>
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
using namespace std;
const int maxn = 100000+10;
int num[maxn];
int n , m , Q;
 
//树链剖分
int siz[maxn], top[maxn], son[maxn];
int dep[maxn], fa[maxn], tid[maxn], seg_pos[maxn];
int tim;
vector<int>es[maxn];
 
void Init()
{
    for(int i = 0 ; i < maxn ; i++)
    {
        es[i].clear();
    }
    tim = 0;
    memset(son, -1, sizeof(son));
}
void DFS1(int now, int from, int deep)   // 寻找重边
{
    dep[now] = deep;
    fa[now] = from;
    siz[now] = 1;
    int length = es[now].size();
    for(int i = 0 ; i < length ; i++)
    {
        int v = es[now][i];
        if(v != from)
        {
            DFS1(v, now, deep + 1);
            siz[now] += siz[v];
            if(son[now] == -1 || siz[v] > siz[son[now]])
            {
                son[now] = v;
            }
        }
    }
}
 
void DFS2(int now, int tp)  // 重边链接成重链
{
    top[now] = tp;
    tid[now] = ++tim; // now 在 线段树 上的编号
    seg_pos[tid[now]] = now;
    if(son[now] == -1) return;
    DFS2(son[now],tp);
    int length = es[now].size();
    for(int i = 0 ; i < length ; i++)
    {
        int v = es[now][i];
        if(v != son[now] && v != fa[now]) DFS2(v,v);
    }
}
 
//线段树
 
int sum[4*maxn],col[4*maxn];
 
void PushUP(int rt)
{
    sum[rt]=max(sum[rt<<1],sum[rt<<1|1]);
}
 
void PushDown(int rt,int m)
{
    if(col[rt])
    {
        col[rt<<1]+=col[rt];
        col[rt<<1|1]+=col[rt];
        sum[rt<<1]+=(m-(m>>1))*col[rt];
        sum[rt<<1|1]+=(m>>1)*col[rt];
        col[rt]=0;
    }
}
 
void Build(int l,int r,int rt)
{
    col[rt]=0;
    if(l==r)
    {
        sum[rt]=num[seg_pos[l]];
        return;
    }
    int mid=(l+r)>>1;
    Build(lson);
    Build(rson);
    PushUP(rt);
}
 
void Update(int L,int R,int v,int l,int r,int rt)
{
    if(L<=l&&R>=r)
    {
        col[rt]+=v;
        sum[rt]+=v*(r-l+1);
        return;
    }
    PushDown(rt,r-l+1);
    int mid=(l+r)>>1;
    if(L<=mid)
        Update(L,R,v,lson);
    if(R>mid)
        Update(L,R,v,rson);
    PushUP(rt);
}
 
int Query(int l,int r,int rt,int val)
{
    if(l==r)
        return sum[rt];
    PushDown(rt,r-l+1);
    int mid=(l+r)>>1;
    int ret=0;
    if(val<=mid) ret=Query(lson,val);
    else         ret=Query(rson,val);
    PushUP(rt);
    return ret;
}
 
void Change(int x, int y, int val)
{
    while(top[x] != top[y])
    {
        if(dep[top[x]] < dep[top[y]]) swap(x,y);
        Update(tid[top[x]], tid[x], val, 1, n, 1);
        x = fa[top[x]];
    }
    if(dep[x] < dep[y]) swap(x,y);
    Update(tid[y], tid[x], val, 1, n, 1);
}
int main()
{
    ios::sync_with_stdio(false);
    char type;
    int a,b,c;
    while(cin >> n >> m >> Q)
    {
        Init();
        for(int i=1; i<=n; i++) cin >> num[i];
        for(int i=1; i<=m; i++)
        {
            cin >> a >> b;
            es[a].push_back(b);
            es[b].push_back(a);
        }
        DFS1(1,0,0);
        DFS2(1,1);
        Build(1,n,1);
        while(Q--)
        {
            cin >> type;
            if(type=='Q')
            {
                cin >> a;
                cout << Query(1,n,1,tid[a]) << endl;
            }
            else
            {
                cin >> a >> b >> c;
                if(type=='D') c=-c;
                Change(a,b,c);
            }
        }
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值