关于树上dfs序的一个例题的思考

题目大意:

给定一棵有 n 个节点的树,初始每个点的点权为 0 ,有 m 个操作,分为两类:

第一类:1 x ,询问 x 节点的权值;

第二类:2 x y w ,对节点 x 到节点 y 路径上所有点的点权加 w。  

n, m <= 4e5

u, v, x, y <= n

z <= 1e9

题目思路:        (需要前置技能,树状数组+树上dfs+lca)

------------------------------------------(先思考一个简化问题)---------------------------------------------------

     首先我们不说这个路径,我们思考一个简化的问题:有两种操作。一种是对x点 的点权加/减w。另一种是查询x的子树的点权和。(起初点权都为0)

       首先我们随便画一棵树。

DFS序为: 1 4 2 9 9 2 7 7 8 8 4 5 6 6 5 1(第一次出现为in,第二次为out)

      因为我们要实现对一个点加值,而改变他的祖先的值。如果我们对2号点+w。那么所有包含这个2号点的所有子树,也就是2的所有祖先(2,4,1),都要加上w。也就是说在dfs序中,包含2这个节点的所有点都要+w,怎么实现这个所有包含2的这个想法呢,想一下,如果包含2,那么肯定他的in和out这个区间包含2的in和out。

重点:两个区间一定不会交叉!!!一定要想清楚,要么包含,要么无交集,一定不会有交叉(栈的特性)

    举个例子。4的in时间戳为2, 4的out时间戳为11 。这个区间包含2的in到out的时间区间。所以4包含2。1号节点同理。

    涉及到区间查询和单点修改,可以想到树状数组或者线段树。我们就用树状数组吧,反正只是单点修改,而且本弱是萌新。

    所以我们实现对2号点+w的时候,就把in【2】的位置+w。那么思考上边的红字部分,因为一定不会有交叉,所以只有完全覆盖这个in【2】位置的节点才会被逻辑上+w。(大佬们可能要发问了,为什么in的位置加?其实还是考虑刚刚的红字部分,完全覆盖这个in和out的才会做贡献,既然包含了in那么一定包含out(红字部分!!),本弱试验过in【2】+w改成out【2】+w,同样可以AC)

    查询的时候我们只要树状数组ask(out【x】)-ask(in【x】-1)既查询区间内的和,就可以了。

------------------------------------------(以上全是本弱口胡,以下才是重点)-----------------------------------------

接下来我们考虑刚刚的题目:第一种询问是查询x点的点权,第二种是x到y的路径全部+w

        我们还是继上边的树状数组思想思考。既然是修改x到y的路径上的全部点,我们上边提到了在in【2】的位置+w。然后会对2的所有祖先都+w对吧,比如还是上边的图(为了读者方便,最好先抄下来图和dfs序)本弱对9到8的这个路径+w。

        我先对in【9】的树状数组位置+w那么(所有祖先)1,2和4都会加上w,我再对in【8】的位置+w,那么1,4和8都会+w,这时候我们发现,1本来不应该被加但是被加了两次,公共祖先4应该被加一次,但是被加了两次,所以我们需要抵消这些多加的,于是对in【LCA(8,9)】这个位置-w。那么所有包含公共祖先的都被减去w,所以再对fa【LCA(8,9)】的位置减去一个w,就成功抵消了所有的多加的。

       查询怎么查??  对于一个点x我只需要计算他的子树一共对他贡献了多少就可以了对吧(子树包含他自己,因为要算被多加的那一次剪掉的)。ask(out【x】)-ask(in【x】-1)既查询区间内的和

#include<iostream>
#pragma GCC optimize(3)
#include<string.h>
#include<cmath>
#include<stdio.h>
#include<algorithm>
#include<queue>
#include<vector>
#define ll long long
using namespace std;
const ll MAXN=4e5+5;
ll d[MAXN],t;
ll c[2*MAXN];
int n,m;
int fa[MAXN],in[MAXN],out[MAXN],f[MAXN][25];
vector<int>v[MAXN];
void add(ll x,ll d)
{
    for(;x<=2*MAXN;x+=x&-x)c[x]+=d;
}
ll ask(ll x){
    ll ans=0;
    for(;x;x-=x&-x){
        ans+=c[x];
    }
    return ans;
}
void bfs()
{
    memset(d,0,sizeof(d));
    d[1]=1;queue<int>q;
    q.push(1);fa[1]=0;
    while(!q.empty())
    {
        ll x=q.front();q.pop();
        ll len=v[x].size();
        for(ll i=0;i<len;i++){
            ll y=v[x][i];
            if(d[y])continue;
            d[y]=d[x]+1;
            fa[y]=x;
            f[y][0]=x;
            for(ll j=1;j<=t;j++){
                f[y][j]=f[f[y][j-1]][j-1];
            }
            q.push(y);
        }
    }
}
ll lca(ll x,ll y)
{
    if(d[x]>d[y])swap(x,y);
    for(ll i=t;i>=0;i--){
        if(d[f[y][i]]>=d[x])y=f[y][i];
    }
    if(x==y)return x;
    for(ll i=t;i>=0;i--){
        if(f[y][i]!=f[x][i])x=f[x][i],y=f[y][i];
    }
    return f[x][0];
}
ll cnt=0;
void dfs(ll x)
{
    in[x]=++cnt;
    ll len=v[x].size();
    for(ll i=0;i<len;i++){
        ll now=v[x][i];
        if(!in[now]){
            dfs(now);
        }
    }
    out[x]=++cnt;
}
int main()
{
    //ios::sync_with_stdio(false);
    memset(c,0,sizeof(c));
    memset(in,0,sizeof(in));
    memset(out,0,sizeof(out));
    //cout<<(int)log(400005)/log(2)+1<<endl;
    scanf("%d%d",&n,&m);
    t=(int)(log(n)/log(2))+1;
    for(ll i=0;i<n-1;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        v[a].push_back(b);v[b].push_back(a);
    }
    bfs();dfs(1);
    for(ll i=1;i<=m;i++){
        int ope;
        scanf("%d",&ope);
        if(ope==1){
            int sb;scanf("%d",&sb);
            printf("%lld\n",(ll)ask(out[sb])-(ll)ask(in[sb]-1));
        }
        else{
            int sb1,sb2,sb3;
            scanf("%d%d%d",&sb1,&sb2,&sb3);
            int ff=lca(sb1,sb2);
            //cout<<1<<endl;
            add(in[sb1],sb3);
            add(in[sb2],sb3);
            add(in[ff],-sb3);
            if(fa[ff]!=0)add(in[fa[ff]],-sb3);
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_41645482/article/details/88581193