[HAOI2015]樹上操作

題目描述

有一棵點數爲 N 的樹,以點 1 爲根,且樹點有邊權。然後有 M 個操作,分爲三種:

  • 操作 1 :把某個節點 x 的點權增加 a 。

  • 操作 2 :把某個節點 x 爲根的子樹中所有點的點權都增加 a 。

  • 操作 3 :詢問某個節點 x 到根的路徑中所有點的點權和。

輸入格式

第一行包含兩個整數 N, M 。表示點數和操作數。 接下來一行 N 個整數,表示樹中節點的初始權值。 接下來 N-1 行每行兩個正整數 from, to , 表示該樹中存在一條邊 (from, to) 。 再接下來 M 行,每行分別表示一次操作。其中第一個數表示該操作的種類( 1-3 ) ,之後接這個操作的參數( x 或者 x a ) 。

輸出格式

對於每個詢問操作,輸出該詢問的答案。答案之間用換行隔開。

思路

如果你會樹鏈剖分的話,這個題就是個樹鏈剖分的裸題,但是還有更好的解法,幫助鍛鍊思維。

線段樹+歐拉序。

對每個點維護兩個東西,一個入度的dfs序,一個出度的dfs序。

這樣,我們將一個點拆成兩個,對第一個點加權,第二個點減權,這樣,對於點x,答案就是1到in[x]的區間和。

比如下面的圖

tree

我們按照dfs序列遍歷就會得到以下的序列

1 2 4 4 5 5 2 3 6 6 7 7 8 8 3 1

每個點出現兩次,分別是第一次進入的和最後一次出去的。

把第一次出現的權值置爲正,第二次出現的置爲負,序列變爲

a1 a2 a4 -a4 a5 -a5 -a2 a3 a6 -a6 a7 -a7 a8 -a8 -a3 -a1

那麼詢問5到1節點的距離,是不是就是

dist=a1+a2+a4-a4+a5

也就是從1到in[5]的前綴和。

那這樣,操作一就很方便了,但是操作二呢?

我們加了一整個子樹的區間,但是有些數是要加,而有些數是要減的,這樣才符合我們的初衷

比如,我們給子樹2都加上v,序列應該變爲

a1 a2+v a4+v a4-v a5+v a5-v a2-v a3 a6 -a6 a7 -a7 a8 -a8 -a3 -a1

此時就不是常規線段樹了,我們可以變通一下。

用num[i]表示從1到i這個區間,增加的數的個數與減小的數的個數的差值,也就是說,對於每個數,標記一個flag,如果加flag=1,否則flag=-1,num數組就是flag的前綴和。

其實一個區間受到 +a 的影響就是 a* (該區間內 + 的個數 - 該區間內 - 的個數)

所以區間修改的時候,對於一個需要修改的區間,sum值應該加上v*(num[r]-num[l-1]),也就是這個區間受影響的個數。

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int maxn=2e5+10;
typedef long long ll;
struct Node{
    int l,r;
    ll sum;
    ll lazy;
}Tree[maxn<<2];
void Build(int root,int l,int r){
    Tree[root].l=l,Tree[root].r=r;
    if(l==r){
        Tree[root].sum=Tree[root].lazy=0;
        return ;
    }
    int mid=(l+r)>>1;
    Build(root<<1,l,mid);
    Build(root<<1|1,mid+1,r);
}
//操作1
void update1(int root,int pos,int v){
    if(Tree[root].l==Tree[root].r){
        Tree[root].sum+=v;
        return ;
    }
    int mid=(Tree[root].l+Tree[root].r)>>1;
    if(pos<=mid){
        update1(root<<1,pos,v);
    }
    else{
        update1(root<<1|1,pos,v);
    }
    Tree[root].sum=Tree[root<<1].sum+Tree[root<<1|1].sum;
}
int num[maxn];//區間增加的點個數
void push_down(int root){
    if(Tree[root].lazy){
        Tree[root<<1].lazy+=Tree[root].lazy;
        Tree[root<<1|1].lazy+=Tree[root].lazy;
        Tree[root<<1].sum+=Tree[root].lazy*(Tree[root<<1].r-Tree[root<<1].l+1);
        Tree[root<<1|1].sum+=Tree[root].lazy*(Tree[root<<1|1].r-Tree[root<<1|1].l+1);
        Tree[root].lazy=0;
    }
}
//操作2
void update2(int root,int l,int r,int v){
    if(Tree[root].l>=l&&Tree[root].r<=r){
        Tree[root].lazy+=v;
        Tree[root].sum+=1ll*v*(num[Tree[root].r]-num[Tree[root].l-1]);
        return ;
    }
    push_down(root);
    int mid=(Tree[root].l+Tree[root].r)>>1;
    if(l>mid){
        update2(root<<1|1,l,r,v);
    }
    else if(r<=mid){
        update2(root<<1,l,r,v);
    }
    else{
        update2(root<<1,l,mid,v);
        update2(root<<1|1,mid+1,r,v);
    }
    Tree[root].sum=Tree[root<<1].sum+Tree[root<<1|1].sum;
}
ll query(int root,int l,int r){
    if(Tree[root].l>=l&&Tree[root].r<=r){
        return Tree[root].sum;
    }
    push_down(root);
    int mid=(Tree[root].l+Tree[root].r)>>1;
    if(l>mid){
        return query(root<<1|1,l,r);
    }
    else if(r<=mid){
        return query(root<<1,l,r);
    }
    else return query(root<<1,l,mid)+query(root<<1|1,mid+1,r);
}
ll val[maxn];
int flag[maxn];
int dfs_clock;
int in[maxn],out[maxn];
int dep[maxn];
vector<int>G[maxn];
void dfs(int u,int fa,int d){
    dep[u]=d;
    in[u]=++dfs_clock;
    flag[in[u]]=1;//表明這個點是加
    for(int i=0;i<G[u].size();i++){
        int v=G[u][i];
        if(v==fa)continue;
        dfs(v,u,d+1);
    }
    out[u]=++dfs_clock;
    flag[out[u]]=-1;//表明這個點是減
}
signed main(){
    int n,m;
    //freopen("E:\\test_data\\testdata.in","r",stdin);
    //freopen("E:\\test_data\\testdata.out","w",stdout);
    scanf("%lld%lld", &n, &m);
    for (int i = 1; i <= n; ++i) scanf("%lld", &val[i]);
    for (int i = 1; i < n; ++i){
        int u, v;
        scanf("%lld%lld", &u, &v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs(1, -1,1);
    for (int i = 1; i <= dfs_clock; ++i)
        num[i] = num[i-1] + flag[i];
    Build(1, 1, n + n);
    for(int i=1;i<=n;i++){
        update1(1,in[i],val[i]);
        update1(1,out[i],-val[i]);
    }
    while (m--){
        int opt, x, a; scanf("%lld", &opt);
        if (opt == 1) {
            scanf("%lld%lld", &x, &a);
            update1(1, in[x], a);
            update1(1, out[x], -a);
        } else if (opt == 2) {
            scanf("%lld%lld", &x, &a);
            update2(1, in[x], out[x], a);
        } else {
            scanf("%lld", &x);
            printf("%lld\n", query(1,  1, in[x]));
        }
    }

    return 0;
}

 

發佈了216 篇原創文章 · 獲贊 15 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章