其他分享
首页 > 其他分享> > 【hdu6547】Tree(树链剖分, 线段树区间根号)

【hdu6547】Tree(树链剖分, 线段树区间根号)

作者:互联网

problem

在这里插入图片描述

algorihtm

1、树链剖分

什么是树链剖分(重链剖分)?

树链剖分(重链剖分)如何实现?

树链剖分(重链剖分)有什么性质?

性质2的证明: 因为最坏情况就是这个点到根路径上经过的边都是轻边,那么每走一条轻边到达这个点的父节点就代表这个父节点至少还有一个与当前子树同样大的子树,也就是说每走一条轻边走到的点的子树大小就要*2,因此最多只能走log次。这也是为什么要选重儿子而不是随便一个儿子的原因。

树链剖分(重链剖分)有什么用?
重链剖分能保证划分出的每条链上的节点 DFS 序连续,因此可以方便地用一些维护序列的数据结构(如线段树)来维护树上路径的信息:

什么是DFS序?

  • 每个节点在dfs深度优先遍历中的进出栈的时间序列。
  • 我们发现,一个点的进出栈的时间点之间的时间段就是它的子树在栈中的所有时间。
  • 也就是说,子树的dfs序(时间戳)肯定大于根节点的进栈时间小于根节点的出栈时间,这就成了一个区间问题。 所以我们就把一个树上的问题“拍”到了一个线性的数据结构上面。

什么是时间戳?

  • 时间戳的概念是:按照深度优先遍历的过程,按每个节点第一次被访问的顺序,依次给予这些节点1−N的标记,这个标记就是时间戳。
  • 时间戳是DFS序的反向映射:
    id[i]=x,i是时间,x是节点,id是DFS序。 即时间对应的点(tarjan)
    dfn[x]=i,i是时间,x是节点,dfn是时间戳。即节点对应的时间(树剖)
  • 网上很多讲解说树链剖分是用的DFS序,严格地说,这个说法是不太严谨的,事实上它是通过维护时间戳来实现树转连续区间的
    在这里插入图片描述
    在这里插入图片描述

2、线段树区间根号

solution

//题意:给定一颗树,q次操作。每次可以询问树上任意两点之间最短路上点权和,或者将树上任意两点之间最短路上每个点权开根。
//思路:修改/查询 树上两点之间的路径上 所有点的值,树链剖分模板。区间开根可以用线段树维护。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5+10;

//题目
int n, q;
LL a[maxn];//树上各节点的值
vector<int>G[maxn];

//树链剖分
int son[maxn], dad[maxn], siz[maxn], dep[maxn];
void dfs1(int x, int fa){//处理出重儿子,父亲,深度,子树大小
    son[x] = 0;  dad[x] = fa;
    siz[x] = 1;  dep[x] = dep[fa]+1;
	for(int to : G[x]){
		if(to==fa)continue;
		dfs1(to,x);
		siz[x] += siz[to];
		if(siz[to]>siz[son[x]])son[x]=to;
	}
}
int top[maxn], pos[maxn], tot=0;//所在链链顶,时间戳编号
LL v[maxn];//映射到序列后的值, 线段树依照此序列建树
void dfs2(int x, int fa, int k){//处理出链顶, 时间戳, 映射后的值
    top[x] = k;
    pos[x] = ++tot;
    v[tot] = a[x];
    if(son[x])dfs2(son[x], x, k);//重链往下去不用更新链顶
    for(int to : G[x]){
        if(to==fa)continue;
        if(to==son[x])continue;
        dfs2(to, x, to);//更新to为链顶
    }
}

//线段树
#define lch p<<1
#define rch p<<1|1
struct node{
    int l, r;
    LL mx, mi, sum, lazy;//维护区间加,区间max,min
}sgt[maxn<<2];
void pushdown(int p){
    sgt[lch].lazy += sgt[p].lazy;
    sgt[rch].lazy += sgt[p].lazy;
    sgt[lch].sum += (sgt[lch].r-sgt[lch].l+1)*sgt[p].lazy;
    sgt[rch].sum += (sgt[rch].r-sgt[rch].l+1)*sgt[p].lazy;
    sgt[lch].mx += sgt[p].lazy;
    sgt[lch].mi += sgt[p].lazy;
    sgt[rch].mx += sgt[p].lazy;
    sgt[rch].mi += sgt[p].lazy;
    sgt[p].lazy = 0;
}
void pushup(int p){
    sgt[p].sum = sgt[lch].sum+sgt[rch].sum;
    sgt[p].mx = max(sgt[lch].mx, sgt[rch].mx);
    sgt[p].mi = min(sgt[lch].mi, sgt[rch].mi);
}
void build(int p, int l, int r){
    sgt[p].l = l;  sgt[p].r = r;
    if(l==r){
        sgt[p].mx = sgt[p].mi = sgt[p].sum = v[l];
        return ;
    }
    int mid = l+r>>1;
    build(lch, l, mid);
    build(rch, mid+1, r);
    pushup(p);
}
LL querysum(int p, int l, int r){
    if(sgt[p].l==l && sgt[p].r==r)return sgt[p].sum;
    if(sgt[p].lazy)pushdown(p);
    int mid = (sgt[p].l+sgt[p].r)>>1;
    if(r<=mid)return querysum(lch, l, r);
    else if(l>mid)return querysum(rch, l, r);
    else return querysum(lch, l, mid)+querysum(rch, mid+1,r);
}
void update(int p, int l, int r){
    if(sgt[p].l==l && sgt[p].r==r){
        LL a = sqrt(sgt[p].mx), b = sqrt(sgt[p].mi);
        if(sgt[p].mx==sgt[p].mi || sgt[p].mx-a==sgt[p].mi-b){//开根后减少的值相同
            LL v= sgt[p].mx-a;//开根后会减少那么多
            sgt[p].sum -= (r-l+1)*v;
            sgt[p].mx -= v;
            sgt[p].mi -= v;
            sgt[p].lazy -= v;
            return ;
        }
    }
    if(sgt[p].lazy)pushdown(p);
    int mid = (sgt[p].l+sgt[p].r)>>1;
    if(r <= mid)update(lch, l, r);
    else if(l > mid)update(rch, l, r);
    else update(lch, l, mid), update(rch, mid+1, r);
    pushup(p);
}

//题目
LL calsum(int u, int v){//计算[u,v]路径和
    LL res = 0;
    while(top[u]!=top[v]){//不在同一条链上 每次链顶深度较大的点往上爬
        if(dep[top[u]]<dep[top[v]])swap(u,v);
        res += querysum(1,pos[top[u]], pos[u]); //sum[pos[top[u]], pos[u]]
        u = dad[top[u]]; //进入新的链
    }
    if(dep[u]>dep[v])swap(u,v);//进入同一条链上时再求一遍区间和 深度小的点在前面
    res += querysum(1, pos[u], pos[v]);
    return res;
}
void updatework(int u, int v){
    while(top[u]!=top[v]){
        if(dep[top[u]]<dep[top[v]])swap(u,v);
        update(1,pos[top[u]], pos[u]);
        u = dad[top[u]]; 
    }
    if(dep[u]>dep[v])swap(u,v);
    update(1,pos[u], pos[v]);
}

int main(){
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin>>n>>q;
    for(int i = 1; i <= n; i++)cin>>a[i];
    for(int i = 1; i < n; i++){
        int u, v;  cin>>u>>v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs1(1,0);
    dfs2(1,0,1);
    build(1,1,n);
    //for(int i = 1 ; i<= n; i++)cout<<top[i]<<" "; cout<<"\n"; return 0;
    for(int i = 1; i <= q; i++){
        int op, u, v;  cin>>op>>u>>v;
        if(op==0)updatework(u,v);
        else cout<<calsum(u,v)<<"\n";
    }
    return 0;
}

标签:lazy,剖分,int,Tree,sgt,rch,节点,根号
来源: https://blog.csdn.net/qq_33957603/article/details/120831506