树链剖分(1)

---恢复内容开始---

对于一个树的图,有如下概念

  • 重结点:子树结点数目最多的结点;
  • 轻节点:父亲节点中除了重结点以外的结点;
  • 重边:父亲结点和重结点连成的边;
  • 轻边:父亲节点和轻节点连成的边;
  • 重链:由多条重边连接而成的路径;
  • 轻链:由多条轻边连接而成的路径;

基本原理,就是对于一组树形的数据结构的操作区间取值,采用将树分成链,然后利用数据结构(线段树、树状数组等)来维护这些链。

siz[u] 保存以u为根的子树节点个数     dfs1   
top[u] 保存当前节点所在链的顶端节点  dfs2
son[u] 保存重儿子            dfs1
dep[u] 保存结点u的深度值        dfs1
faz[u] 保存结点u的父亲节点        dfs1
tid[u] 保存树中每个节点剖分以后的新编号(DFS的执行顺序)  dfs2
rnk[u] 保存当前节点在树中的位置              dfs2

还有两条性质:

  1. 如果(u, v)是一条轻边,那么size(v)  <  size(u)  /  2; ps:因为重点是子节点数最多的了
  2. 从根结点到任意结点的路所经过的轻重链的个数必定都小与O(logn);

再次想一想我们需要什么

***节点的重儿子son数组

***该节点的父亲f数组

***节点的根节点top数组

***该节点的子节点个数siz数组

***该节点的深度dep数组

***dfs中该节点被访问的顺序did数组

***dfs中访问顺序所对应的节点

两次dfs

维护线段树

进行LCA查询操作

 如果改查一个节点和其子树————dfs序查询一个节点和其子树————dfs序 + 线段树维护
 如果改查一条链或者不同链上的任意两个点,线段树就不够了需要用到LCA并且利用top数组实现优化,快速计算

 初始准备工作:

//https://www.luogu.org/problemnew/show/P3384
//https://www.cnblogs.com/George1994/p/7821357.html
#include <iostream>
#include <cstdio>
#include <string.h>
#include <cmath>
#include <algorithm>
#define inf (1 << 28)
#define lson rt<<1,left,mid
#define rson rt<<1|1,mid+1,right
#define ls rt<<1
#define rs rt<<1|1
using namespace std;
typedef long long ll;
int n,m,root,Mod;//节点个数,操作个数,根节点序号,取模数
const int maxn = 1e5 + 1e3;
int V[maxn];
//step1 构建线段树
ll lazy[maxn<<2];//经典之lazy标记
ll val[maxn<<2];//树中的节点对应的值(树中的节点就是dfs的序号)
/*dfs1*/
int siz[maxn];
int dep[maxn];
int son[maxn];
int fa[maxn];
/*dfs2*/
int did[maxn];//dfs序对应的原数据的节点值 --- 通过节点得知dfs序号
int rnk[maxn];//这个节点的值对应的dfs序 --- 通过序号得知节点号
int top[maxn];
int tot;
struct node{
    int to,pre;
}e[maxn << 1];
int id[maxn],cnt;

两次dfs得到我们所需要的东西

void dfs1(int rt,int f,int depth)
{
    fa[rt] = f;
    dep[rt] = depth;
    siz[rt] = 1;

    for(int i = id[rt];~i;i = e[i].pre)
    {
        int to = e[i].to;
        if(to != f)
        {
            dfs1(to,rt,depth+1);
            siz[rt] += siz[to];
            if(siz[to] > siz[son[rt]])
                son[rt] = to;
        }
    }

}
void dfs2(int now,int rt)
{
    top[now] = rt;
    did[now] = ++tot;
    rnk[tot] = now;

    if(son[now])
        dfs2(son[now],rt);
    else
        return;

    for(int i = id[now];~i;i = e[i].pre)
    {
        int to = e[i].to;
        if(to != fa[now] && to != son[now])
        {
            dfs2(to,to);
        }
    }
}

 然后对一些必要数据的初始化

void init()
{
    memset(id,-1,sizeof(id));
    memset(siz,0,sizeof(siz));
    memset(son,0,sizeof(son));
    cnt = 0;
    tot = 0;
}
void add(int from,int to)
{
    e[cnt].to = to;
    e[cnt].pre = id[from];
    id[from] = cnt++;
}

 然后针对线段树的常规操作

void pup(int rt)
{
    val[rt] = val[ls] + val[rs];
}
void build(int rt,int left,int right)
{
    lazy[rt] = 0;
    if(left == right)
        val[rt] = V[rnk[left]];
    else
    {
        int mid = (left + right) >> 1;
        build(lson);
        build(rson);
        pup(rt);
    }

    /*
    PS:rt就是rt,我要访问也是通过left和right来访问操作,所以不必如此
    */
}
void pdown(int rt,int left,int right)
{
    if(lazy[rt])
    {
        int lt = lazy[rt];
        int mid = (left + right) >> 1;
        val[ls] += (mid - left + 1) * lt;
        val[rs] += (right - mid) * lt;

        lazy[rs] += lt;
        lazy[ls] += lt;
        lazy[rt] = 0;
    }
}
void update(int rt,int left,int right,int l,int r,ll k)
{
    if(l <= left && right <= r)
    {
        val[rt] += (right - left + 1) * k;
        lazy[rt] += k;
        return;
    }
    if(left > r || right < l)return;

    pdown(rt,left,right);

    int mid = (left + right) >> 1;
    if(left <= mid)
        update(lson,l,r,k);
    if(right > mid)
        update(rson,l,r,k);
    pup(rt);
}
ll query(int rt,int left,int right,int l,int r)
{
    ll res = 0;

    if(l <= left && right <= r)
    {
        return val[rt];
    }
    if(left > r || right < l)return 0 ;
    pdown(rt,left,right);

    int mid = (left + right) >> 1;

    if(left <= mid)
        res += query(lson,l,r);
    res %= Mod;
    if(right > mid)
        res += query(rson,l,r);
    return res % Mod;
}

 最主要的就是LCA查询操作

线段树可以提供查询连续的区间值,但如果区间不连续,我们可以利用LCA将深度低的点利用top数组往上跳,记录中间的权值

一旦在一条链上则直接利用线段树,改查都是这样的思想

ll query_lca(int x,int y)
{
    ll res = 0;
    while(top[x] != top[y])
    {
        if(dep[top[x]] < dep[top[y]])swap(x,y);
        res += query(1,1,tot,did[top[x]],did[x]);
        x = fa[top[x]];
        res %= Mod;
    }
    if(dep[x] < dep[y])swap(x,y);

    res += query(1,1,tot,did[y],did[x]);

    return res % Mod;
}
void updata_lca(int x,int y,int z)
{
    while(top[x] != top[y])
    {
        if(dep[top[x]] < dep[top[y]])swap(x,y);
        update(1,1,tot,did[top[x]],did[x],z);
        x = fa[top[x]];
    }
    if(dep[x] < dep[y])swap(x,y);
    update(1,1,tot,did[y],did[x],z);
}

 最后就简单略

int main()
{
    while(~scanf("%d%d%d%d",&n,&m,&root,&Mod))
    {
        init();
        for(int i = 1;i <= n;i++ )
        {
            scanf("%d",&V[i]);
        }
        int from, to;
        for(int i = 0;i < n-1;i++)
        {
            scanf("%d%d",&from,&to);
            add(from,to);
            add(to,from);
        }
        dfs1(root,root,1);
        dfs2(root,root);
        //cout<<tot<<endl;对
        build(1,1,tot);
        int op,x,y,z;
        for(int i = 0;i < m;i++)
        {
            scanf("%d",&op);
            if(op == 1)
            {
                scanf("%d%d%d",&x,&y,&z);
                updata_lca(x,y,z);
            }
            else if(op == 2)
            {
                scanf("%d%d",&x,&y);
                printf("%lld\n",query_lca(x,y));

            }
            else if(op == 3)
            {
                scanf("%d%d",&x,&z);
                update(1,1,tot,did[x],did[x]+siz[x] - 1,z);
            }
            else if(op == 4)
            {
                scanf("%d",&x);
                printf("%lld\n",query(1,1,tot,did[x],did[x] + siz[x] - 1));
            }
        }
    }
    return 0;
}

 PS所要注意一点的是 longlong的情况,都要考虑到略,别有的地方longlong了有的地方没有longlong

猜你喜欢

转载自www.cnblogs.com/DF-yimeng/p/9575260.html