題目描述
有一棵點數爲 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]的區間和。
比如下面的圖
我們按照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節點的距離,是不是就是
也就是從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;
}