Codeforces 383C . Propagating tree【树阵,dfs】

Codeforces 383C . Propagating tree【树阵,dfs】

标题效果:

有一棵树,有两种操作模式对本树:1:表示为(1 x val),在NOx加在节点上val,然后x每个节点加上儿子- val。给每个儿子一个儿子在一起-(- val),加到没有儿子为止。2:表示为(2 x)查询x节点上的值。

做法:

因为每次改动操作改动的并非一个值,而是非常多值。那我们将该题抽象成区间改动,点查询的问题。那怎么抽象呢?能够明确的是,每次操作尽管有加有减,可是每次做加法操作。或者减法操作的都是同一部分数(也就是说,在某次加上同一个数的节点们。下次操作一定是加上或者减去同样的数),可是假设以树原来的编号为基础的话,那我们须要改动同样的数的那些节点肯定是不连续的,那就无法使用线段树或者树状数组的区间改动了。应该怎么办呢?我们考虑一下能不能将这树上的节点又一次排序,使得每次改动的值在一个连续的区间。

比方说有例如以下一棵树:

Codeforces 383C . Propagating tree【树阵,dfs】

树的右边第一列代表深度,第二列代表着有着同样值(0或者1)的这些层的节点改动时都是同加或者同减的。比方第二层的节点和第四层的每一个节点在改动时进行的操作一定是全是加。或者全是减,不可能一部分节点加,一部分节点减的。

Codeforces 383C . Propagating tree【树阵,dfs】

那我们怎么将每次操作须要改动的值都又一次编号为一个连续的区间呢? 如上图我们应该是又一次编号为这种:

Codeforces 383C . Propagating tree【树阵,dfs】

(第一行为新数组下标。第二行为新数组存的节点序号)

这样一来,我们就把每次须要改动的值变成了连续的了,比方说改动操作为(1 1 val),那么我们须要加val的节点在新数组中的区间为[1,5],须要减val的节点在[6,10];假设改动操作为(1 2 val)那么我们须要加val的节点在新数组中的区间为[6,8],须要减val的节点在[2,3]。
那详细怎么才干编号成这样呢?我们首先将每一个节点相应的上图右边第二列的值存在d[]数组中,先从根节点(1)開始。跳层DFS。以dfs的顺序将遍历到的儿子节点一个个的加到新数组中。

遍历完之后,再对根节点的每一个儿子做一遍同样的操作就可以。(详细能够看代码)


得到这个数组之后呢,我们还要预处理出对某个点进行改动操作,我们须要在那个区间加值。在哪个区间减值。也是用dfs,每一个节点的属性能够由儿子来确定。详细看代码和凝视:
代码:
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
#define N 200020
using namespace std;
struct ee//存储须要改动哪些区间的结构体
{
    int x1,y1,x2,y2;
}e[N];
vector<int> g[N];
int n,m,a[N],d[N],c[N],bef[N],index=1;
void dfs1(int x,int fa,int deep)//处理d[]数组
{
    d[x]=deep;
    for(int i=0;i<g[x].size();i++)
        if(g[x][i]!=fa) dfs1(g[x][i],x,1-deep);
}
void dfs2(int x,int fa,int deep)//得到新数组
{
    if(d[x]==deep) bef[x]=index++;//aft[index++]=x;//bef[i] 当中,i是原节点的编号,bef[i]是i在新数组中的下标
    for(int i=0;i<g[x].size();i++)
        if(g[x][i]!=fa) dfs2(g[x][i],x,deep);
}
void dfs3(int x,int fa)//预处理每一个点的属性
{
    for(int i=0;i<g[x].size();i++)
        if(g[x][i]!=fa) dfs3(g[x][i],x);
    int ma1=bef[x],mi2=N,ma2=0;
    for(int i=0;i<g[x].size();i++)
    {
        if(g[x][i]==fa) continue;
        int cur=g[x][i];
        mi2=min(mi2,e[cur].x1);
        ma2=max(ma2,e[cur].y1);
        ma1=max(ma1,e[cur].y2);
    }
    e[x].x1=bef[x],e[x].y1=ma1,e[x].x2=mi2,e[x].y2=ma2;//[x1,y1]为须要加值操作的区间,[x2,y2]为须要减值操作的区间,能够由儿子确定
}
int getnum(int x)//以下便是树状数组的区间改动,点查询函数咯~
{
    int rnt=0;
    for(int i=x;i<=n;i+=(i&(-i)))
    {
        rnt+=c[i];
    }
    return rnt;
}
void add(int i,int a)
{
    while(i>=1)
    {
        c[i]+=a;
        i-=(i&(-i));
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",a+i);
    for(int i=0;i<n-1;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        g[a].push_back(b),g[b].push_back(a);
    }
    dfs1(1,0,1);//计算d[]数组
    dfs2(1,0,1);//对根节点进行处理
    for(int i=0;i<g[1].size();i++)
        dfs2(g[1][i],1,0);//对根节点的每一个儿子进行处理
    dfs3(1,0);//预处理
    
    while(m--)
    {
        int ty;
        scanf("%d",&ty);
        if(ty==1)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            int l1=e[x].x1,r1=e[x].y1,l2=e[x].x2,r2=e[x].y2;
            add(r1,y),add(l1-1,-y);
            if(r2!=0) add(r2,-y),add(l2-1,y);//假设不是根节点再进行减操作
        }
        else
        {
            int x;
            scanf("%d",&x);
            cout<<getnum(bef[x])+a[x]<<endl;
        }
    }
    return 0;
}


版权声明:本文博客原创文章,博客,未经同意,不得转载。