test 一道树链剖分 顺便讲讲树链剖分的理解

题面描述:
有一棵n个节点的树,初始时根节点为1。现在要支持如下操作——1、将某节点设置为根;2、改变某节点权值;3、询问以某节点为根的子树内节点权值之和;4、询问以某两点为端点的链上的节点权值之和。

其实一直我不是很懂树链剖分然后遇到这方面的题目也一直半懂不懂加上今天的第二题也非常之难搞(判重)然后又。。来不及打

刚刚琢磨了一会突然懂了。。。

树链剖分这个东西主要是用于树上维护数列的值等等
一般我们是在树上维护一个树状数组和线段树,不过我个人倾向于树状数组,代码短好理解

首先我们维护以下几样的东西:
重结点:子树结点数目最多的结点;
轻节点:父亲节点中除了重结点以外的结点;
重边:父亲结点和重结点连成的边;
轻边:父亲节点和轻节点连成的边;
重链:由多条重边连接而成的路径;
轻链:由多条轻边连接而成的路径;

那么我画个图:
这里写图片描述

红色圈代表重链,蓝色圈代表轻链
如果初学大概有可能一时难以理解
我上代码解释

void dfs(int x){
    int si=e[x].size(),ms=0;
    tr[x].sz=1;
    for(int i=0;i<=si-1;++i){
        int u=e[x][i];
        if(u!=tr[x].fa){
            tr[u].fa=x;
            tr[u].dp=tr[x].dp+1;
            dfs(u);
            tr[x].sz+=tr[u].sz;
            if(tr[u].sz>ms) ms=tr[u].sz,tr[x].bson=u;//普通遍历一棵树的时候求出每个节点的的重儿子
        }
    }
}
void dfs2(int x,int an){
    ++t;oton[x]=t;
    tr[x].an=an;
    if(tr[x].bson!=0) dfs2(tr[x].bson,an);
    int si=e[x].size();
    for(int i=0;i<=si-1;++i){
        int u=e[x][i];
        if(u!=tr[x].fa&&u!=tr[x].bson) dfs2(u,u);
    }
}//此处即求重链,用dfs序去求,先dfs重儿子,an是每条链的链头(轻/重),重链的链头即是根,轻链即这条链开始的那个点

然后重头戏就是用重链优先得出来的dfs序做事情

举个例子

这里写图片描述

正常dfs序是:
1 2 4 3 5 7 6 > d f s 1234567
树链剖分dfs序是
1357624 > d f s 1234567

然后你在dfs序上做树状数组(每个节点是带权的)
直接做
然后你可以轻易地应用于树上的区间操作,例如求和,修改某一个节点权值等等
会有想不到的神奇之处
譬如你要求以3节点为根的子树权值和,即3——6的树状数组区间值,速度很快。修改起来一样用树状数组的方法修改。

这是大概我目前对于树状数组的理解了。。
因为例如把两个节点互换之类的要用treap和splay去维护的树链剖分问题我还不会。。。有点难。。。

然后我们来讲讲这道题
只要你会了我讲的树链剖分,那么234三个操作肯定是没问题的,主要是操作1

我之前做过一道题树形dp,要以每个节点为根都做一次dp。那道题做法是以每个节点作为根分别取遍历一遍,但肯定不适用这道题

那么怎么做呢?
首先操作1,我们只需要一个操作,就是

if(opt==1) read(x),root=x;

然后我们在后面操作34上再做其他操作
以操作3为例子
分两种情况:
1.新的根不在当前我们要求和的这个节点的子树里,
这里写图片描述

譬如,当前根为4,我们要求和3
其实无所谓,因为3的子树永远不会变,管自己树状数组求和即可
2.新的根在我们要求和的这个节点的子树里,
譬如根为5,要求和3
这时候,除了当前3延伸出来的一条链,其他节点就都成为了3的子树
那么只要求得3包含5的这条链,用整棵树减去这条链的总和即可
具体看代码
代码如下:

int qt(int u){
    if(u==rt) return ask(n);
    int v=rt,ff=0;
    while(v!=1){
        if(tr[v].dp<=tr[u].dp) {break;}//如果当前根不在子树内且在这个节点的父亲那一层及以上,那么后面求和则管自己求,因为自己的子树不会变
        if(tr[v].an==tr[u].an) {ff=tr[u].bson;break;}//如果两个点在同一条链上,把求和点的重儿子得出来后面再求和
        v=tr[v].an;//把原本不在同一条链上的两个点靠近
        if(tr[v].fa==u) {ff=v;break;}//如果v的父亲是u,那么v即这一条链的链头使我们要处理的
        v=tr[v].fa;//跨链操作,自己理解
    }
    if(ff) return ask(n)-query(oton[ff],oton[ff]+tr[ff].sz-1);//非常神奇的操作,把这个重儿子和他的子树和求出,然后用总和减去
    else return query(oton[u],oton[u]+tr[u].sz-1);//如果ff不为0说明新的根影响不到子树求和
}

那么操作4自己理解吧,毕竟路还要你自己去走的。
总代码

#include<bits/stdc++.h>
using namespace std;
template<typename T>void read(T &x){
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-48;
    x*=f;
}
int c[100010],oton[100010],rt=1,w[100010],n,q;//szsz
struct{int fa,an,sz,bson,dp;}tr[100010];
vector<int>e[100010];
void add(int x,int ad){
    for(;x<=n;x+=(x&-x)) c[x]+=ad;
}
int ask(int x){
    int ans=0;
    for(;x;x-=(x&-x)) ans+=c[x];
    return ans;
}
int query(int l,int r){
    return ask(r)-ask(l-1);
}
void dfs1(int x){
    int si=e[x].size(),ms=0;
    tr[x].sz=1;
    for(int i=0;i<=si-1;++i){
        int u=e[x][i];
        if(u!=tr[x].fa){
            tr[u].fa=x;
            tr[u].dp=tr[x].dp+1;
            dfs1(u);
            tr[x].sz+=tr[u].sz;
            if(tr[u].sz>ms) ms=tr[u].sz,tr[x].bson=u;
        }
    }
}
int t=0;
void dfs2(int x,int an){
    ++t;oton[x]=t;
    tr[x].an=an;
    if(tr[x].bson!=0) dfs2(tr[x].bson,an);
    int si=e[x].size();
    for(int i=0;i<=si-1;++i){
        int u=e[x][i];
        if(u!=tr[x].fa&&u!=tr[x].bson) dfs2(u,u);
    }
}
int q4(int u,int v){
    int ans=0;
    while(tr[u].an!=tr[v].an)
        if(tr[tr[u].an].dp>tr[tr[v].an].dp){
            ans+=query(oton[tr[u].an],oton[u]);
            u=tr[tr[u].an].fa;
        }
        else{
            ans+=query(oton[tr[v].an],oton[v]);
            v=tr[tr[v].an].fa;          
        }
    if(oton[u]>oton[v]) swap(u,v);
    ans+=query(oton[u],oton[v]);
    return ans; 
}
int q3(int u){
    if(u==rt) return ask(n);
    int v=rt,ff=0;
    while(v!=1){
        if(tr[v].dp<=tr[u].dp) {break;}
        if(tr[v].an==tr[u].an) {ff=tr[u].bson;break;}
        v=tr[v].an;
        if(tr[v].fa==u) {ff=v;break;}
        v=tr[v].fa;
    }
    if(ff) return ask(n)-query(oton[ff],oton[ff]+tr[ff].sz-1);
    else return query(oton[u],oton[u]+tr[u].sz-1);
}
int main(){

    read(n);read(q);
    for(int i=1;i<=n-1;++i){
        int x,y;
        read(x);read(y);
        e[x].push_back(y);
        e[y].push_back(x);
    }
    for(int i=1;i<=n;++i)
        read(w[i]);
    tr[1].dp=1;
    dfs1(1);
    dfs2(1,1);
    for(int i=1;i<=n;++i)
        add(oton[i],w[i]);
    for(int i=1;i<=q;++i){
        int opt,x,y;
        read(opt);
        if(opt==1) read(x),rt=x;
        else if(opt==2) read(x),read(y),add(oton[x],y-w[x]),w[x]=y;
        else if(opt==3) read(x),printf("%d\n",q3(x));
        else if(opt==4) read(x),read(y),printf("%d\n",q4(x,y));
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/beautiful_CXW/article/details/81672981