bzoj 4034 [HAOI2015]树上操作 题解(DFS序,线段树,模板)

原题链接
洛谷:点我QωQ
bzoj:点我QωQ

题意简述

我们在一个树上搞Gay,给定每个点的初始权值,支持三种操作:

  1. 单点加权值
  2. 子树加权值
  3. 求某个点到根的权值和

点数,操作数都 1 e 5 1e5 1e5,所有输入的数的最大值不会超过 1 e 6 1e6 1e6(不会爆 l o n g   l o n g long\ long long long

数据

输入

第一行两个正整数 n , m n,m n,m表示点数和操作数。
接下来一行 n n n个正整数表示每个点的初始点权。
接下来 n − 1 n-1 n1行每行两个正整数 u , v u,v u,v表示 u u u v v v之间有连边。
接下来 m m m行每行一个正整数 o o o,表示操作种类。如果
o = 1 o=1 o=1,给定 x , a x,a x,a,表示点 x x x的权加 a a a
o = 2 o=2 o=2,给定 x , a x,a x,a,表示 x x x子树的权加 a a a
o = 3 o=3 o=3,给定 x x x,询问 x x x到根的路径上的点权和。

输出

对于每个 o = 3 o=3 o=3的操作,输出答案。

样例

输入
5 5
1 2 3 4 5
1 2
1 4
2 3
2 5
3 3
1 2 1
3 5
2 1 2
3 3
输出
6
9
13

思路

当我们第一次看到这个题的时候,我们会想:这 t m ^{tm} tm搞什么,在树上操作?不管了,暴力模拟。。。然后看到数据。。。

接着就开始 f a n t a s i e s fantasies fantasies,如果这个树上的问题,能打到一个 序 列 \color{red}序列 上做,那不就好搞了。所以我们观察怎么打到序列上。想解决这个,就要发现子树的 连 续 性 \color{red}连续性 。子树上的点编号肯定不能保证连续,但是,我们会发现,一整个子树在 D F S DFS DFS的过程中,一定是被连续遍历的。所以想了一个东西: D F S DFS DFS序。

如何实现这个东西呢?我们把这个树重新编号!建一个 D F S i d DFSid DFSid数组, D F S i d [ u ] DFSid[u] DFSid[u]表示点 u u u的新编号,由于是按 D F S DFS DFS的顺序做的编号,所以叫 D F S i d DFSid DFSid。但是这样还不行,因为我们只知道子树是连续的,并且从 D F S i d [ u ] DFSid[u] DFSid[u]开始,但连续多长呢?或者终点在哪?无从得知。。。

所以我们考虑把这个 D F S i d DFSid DFSid加一维 [ 0 / 1 ] [0/1] [0/1],我们在 D F S DFS DFS到点 u u u的时候记录一次,设为 D F S i d [ u ] [ 0 ] DFSid[u][0] DFSid[u][0],然后遍历完 u u u点要返回的时候,再记录一次,设为 D F S i d [ u ] [ 1 ] DFSid[u][1] DFSid[u][1],然后这样我们只要在子树加的时候知道区间的终点在 D F S [ u ] [ 1 ] DFS[u][1] DFS[u][1],这样就好记录了。(单点。。。还是说一下吧,只要把 D F S i d [ u ] [ 0 ] DFSid[u][0] DFSid[u][0]加一下即珂。但是为了 3 3 3操作,后面还会继续讲)

那么 3 3 3操作怎么办呢?一条链,虽然珂能 D F S DFS DFS的时候连续遍历,但是也不排除不连续的珂能。这。。。怎么办。。。
举个栗子吧。如下图:
blog1.jpg
(一个比较毒瘤的树。。。)
我们要查询点 17 17 17到根的权值和。我们会发现,那条链是怎么形成的?我们在 D F S DFS DFS遍历的时候,这个就相当于那些还没有出栈的点组成的。(什么是出栈了的点呢?就是那些完全遍历完,没有什么用了的点)。如果我们在查询和的时候,这些出栈的点能够被 抵 消 \color{red}抵消 ,那就爽了。珂是。。。如何抵消呢?

我们不是 D F S i d DFSid DFSid开了个 2 2 2么。。。那么,我们只要设一个 i o io io数组, i o [ u ] io[u] io[u]表示 D F S i d DFSid DFSid u u u的是进来( i i i)还是出来( o o o)。如果是进来的话,就设为 1 1 1,否则就设为 − 1 -1 1。与此同时,我们在线段树上维护一个 f l a g flag flag(代码中记为 F F F),表示区间的系数。这个系数是由区间中 所 有 点 的 i o 值 相 加 而 得 \color{red}所有点的io值相加而得 io(如果区间是单点,那么这个系数就是该点上的 i o io io值了)。这个系数是干嘛的呢?它让一些值 自 然 抵 消 \color{red}自然抵消 ,然后我们在实现区间加值的时候,设我们要加 x x x,并不是直接 S + = x ∗ ( R − L + 1 ) S+=x*(R-L+1) S+=x(RL+1),而是 S + = x ∗ F S+=x*F S+=xF。然后由于有了 F F F系数,第三个操作就珂以直接输出前缀和。原因是这个前缀和中就已经包含抵消了。

当然,说一下 1 1 1操作和 2 2 2操作的改变。 1 1 1操作就是多一步,不仅要在 D F S i d [ u ] [ 0 ] DFSid[u][0] DFSid[u][0]位置上单点加值,在 D F S i d [ u ] [ 1 ] DFSid[u][1] DFSid[u][1]上也要单点加值。当然,因为我们加的时候乘了一个系数,在 D F S i d [ u ] [ 1 ] DFSid[u][1] DFSid[u][1]上加的那个实际上是加了个负的。
问题来了:操作 2 2 2要不要变?
答案来了:代码不用,因为函数变了。理由:还记不记得我们在线段树实现区间加值的函数里,改变了一下乘的系数,从原来的 ( R − l + 1 ) (R-l+1) (Rl+1)改成了 F F F,所以函数已经变了,代码就不用变了。(但是一定要明白操作二简单的代码背后在干什么,这个理解比较困难,建议自己模拟一下看看)

说了这么多,总结一下这个题怎么做:

  1. 处理好进入/出去的顺序 D F S i d DFSid DFSid数组,以及出入系数 i o io io数组。
  2. 在乘的时候,维护好 F F F系数。
  3. 对于第 3 3 3个操作,输出前缀和即珂。
  4. 对于第 1 , 2 1,2 1,2个操作,单点/区间加值即珂,不要忘了 1 1 1操作要多一步。

代码:

#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
    #define int long long
    #define N 1001000
    class Graph//存图
    {
        public:
            int head[N];
            int EdgeCount;
            struct Edge
            {
                int To,Label,Next;
            }Ed[N];

            void clear()
            {
                memset(Ed,-1,sizeof(Ed));
                memset(head,-1,sizeof(head));
                EdgeCount=0;
            }
            void AddEdge(int u,int v,int w)
            {
                ++EdgeCount;
                Ed[EdgeCount]=(Edge){v,w,head[u]};
                head[u]=EdgeCount;
            }

            int Start(int u)
            {
                return head[u];
            }
            int To(int u)
            {
                return Ed[u].To;
            }
            int Label(int u)
            {
                return Ed[u].Label;
            }
            int Next(int u)
            {
                return Ed[u].Next;
            }
    }G;void Add(int u,int v,int w){G.AddEdge(u,v,w);G.AddEdge(v,u,w);}

    int w[N],p[N],io[N];//原点权,乘上了系数的点权,出入系数
    class SegmentTree
    {
        public:
            struct node
            {
                int l,r;
                int s,a,f;
            }tree[N<<1];
            #define ls index<<1
            #define rs index<<1|1

            #define L tree[index].l
            #define R tree[index].r
            #define S tree[index].s
            #define A tree[index].a
            #define F tree[index].f

            #define lL tree[ls].l
            #define lR tree[ls].r
            #define lS tree[ls].s
            #define lA tree[ls].a
            #define lF tree[ls].f

            #define rL tree[rs].l
            #define rR tree[rs].r
            #define rS tree[rs].s
            #define rA tree[rs].a
            #define rF tree[rs].f

            void Update(int index)
            {
                S=lS+rS;
                F=lF+rF;//由于F维护的是区间F自然抵消的和,所以这里要把F也加上
            }
            void BuildTree(int l,int r,int index)
            {
                L=l,R=r,S=A=0;
                if (l==r)
                {
                    S=p[L];
                    F=io[L];
                    return;//单点的情况
                }
                int mid=(l+r)>>1;
                BuildTree(l,mid,ls);
                BuildTree(mid+1,r,rs);
                Update(index);
            }
            void AddOne(int x,int index)
            {
                S+=x*F;//注意这里的改变:不是S+=x*(R-L+1)
                A+=x;
            }
            void PushDown(int index)
            {
                if (A)
                {
                    AddOne(A,ls);
                    AddOne(A,rs);
                    A=0;
                }
            }//差不多的思路
            void Add(int l,int r,int x,int index)
            {
                if (l>R or L>r) return;
                if (l<=L and R<=r) return AddOne(x,index);
                PushDown(index);
                Add(l,r,x,ls);
                Add(l,r,x,rs);
                Update(index);
            }//非常显然的代码
            //对了说一下,AddOne由于是void类型,所以我们返回这个东西不会报错
            int Query(int l,int r,int index)
            {
                if (l>R or L>r) return 0;
                if (l<=L and R<=r) return S;
                PushDown(index);
                return Query(l,r,ls)+Query(l,r,rs);
            }//也是非常显然的代码

            #undef ls //index<<1
            #undef rs //index<<1|1

            #undef L //tree[index].l
            #undef R //tree[index].r
            #undef S //tree[index].s
            #undef A //tree[index].a
            #undef F //tree[index].f

            #undef lL //tree[ls].l
            #undef lR //tree[ls].r
            #undef lS //tree[ls].s
            #undef lA //tree[ls].a
            #undef lF //tree[ls].f

            #undef rL //tree[rs].l
            #undef rR //tree[rs].r
            #undef rS //tree[rs].s
            #undef rA //tree[rs].a
            #undef rF //tree[rs].f
    }T;
    int n,m;
    int DFSid[N][2];int cnt=0;//cnt记录当前的DFS序用到了哪个点
    void DFS(int u,int f)
    {
        DFSid[u][0]=++cnt;
        p[DFSid[u][0]]=w[u];
        io[cnt]=1;//进来了哦QωQ

        for(int i=G.Start(u);~i;i=G.Next(i))
        {
            int v=G.To(i);
            if (v!=f)
            {
                DFS(v,u);//继续搜
            }
        }
        DFSid[u][1]=++cnt;
        p[DFSid[u][1]]=-w[u];
        io[cnt]=-1;//珂以出去了哦QωQ
    }
    void Query()
    {
        scanf("%lld%lld",&n,&m);
        for(int i=1;i<=n;++i)
        {
            scanf("%lld",&w[i]);
        }

        G.clear();//写链式前向星不清图见比♂利了。。。
        for(int i=1;i<n;++i)
        {
            int a,b;scanf("%lld%lld",&a,&b);
            Add(a,b,1);
        }
        cnt=0;memset(DFSid,0,sizeof(DFSid));
        DFS(1,-1);
        T.BuildTree(1,(n<<1),1);
        //千万记得!!!BuildTree在DFS后面!!!

        for(int i=1;i<=m;++i)
        {
            int o,x,a;
            scanf("%lld",&o);
            if (o==1)
            {
                scanf("%lld%lld",&x,&a);
                T.Add(DFSid[x][0],DFSid[x][0],a,1);
                T.Add(DFSid[x][1],DFSid[x][1],a,1);
            }
            else if (o==2)
            {
                scanf("%lld%lld",&x,&a);
                T.Add(DFSid[x][0],DFSid[x][1],a,1);
            }
            else
            {
                scanf("%lld",&x);
                printf("%lld\n",T.Query(1,DFSid[x][0],1));
            }
        }//处理操作
    }
    void Main()
    {
        if (0)
        {
            freopen("","r",stdin);
            freopen("","w",stdout);
        }
        Query();
    }
    #undef int //long long
    #undef N //1001000
};
main()
{
    Flandle_Scarlet::Main();
    return 0;
}

回到总题解界面

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值