Spices(線性基、貪心)
阿新 • • 發佈:2022-03-16
分析
看題,是一道樹剖+線段樹裸題。不多說,貼一道板子P3384 【模板】輕重鏈剖分/樹鏈剖分。這就可以解決這道題目了。
我們要說的是另外一種方法。
這裡介紹一種不同於樹剖的方法,首先需要知道一個概念:尤拉序,這是 DFS 序的一種,舉個例子:
這樣的一棵樹,它的尤拉序為1 2 4 4 5 5 2 3 6 6 7 7 8 8 3 1
顯然,每個點在尤拉序中出現了 2 次;尤拉序有一個非常優越的性質,如果把每個點第一次出現記作 +
,第二次出現記作 -
,那麼根節點到任意節點的權值和在尤拉序上對應一個字首和,這個性質非常好理解,因為尤拉序其實又叫"出棧入棧序",所以字首中尚未抵消掉的點在 DFS 到當前點時在棧中,那麼其肯定在當前點到根的路徑中。
我們分別來說如何利用尤拉序來完成三個操作
用dfn1表示結點第一次出現的尤拉序中的編號,dfn2表示結點第二次出現的尤拉序中的編號
操作一
把某個節點x的權值增加a
這個操作分為兩個步驟,首先需要線上段樹樹上,第一次出現x的結點處+a,第二次出現x的結點處-a
我們加入一個數組num來統計從結點1開始到i中的+號的個數。
則,我們在進行修改操作的時候,對應結點處的符號可以直接通過num[i]-num[i-1]得到。
modify(1,dfn1[x],dfn1[x],c);
modify(1,dfn2[x],dfn2[x],c);
操作二
**把某個節點 x 為根的子樹中所有點的點權都增加 a **
其中需要更改的是一個區間,那這個區間中有些節點需要+a,有些節點需要-a(因為子樹中的結點被掃描的第一次和第二次結點標號都在區間中)。
那怎麼辦呢?
這時候我們只需要做一個小小的變化,其實一個區間受到 +a 的影響就是 \(a × (該區間內 + 的個數 - 該區間內 - 的個數)\)
modify(1,dfn1[x],dfn2[x],c);
操作三
詢問某個節點 x 到根的路徑中所有點的點權和。
這點就比較簡單了。
直接上程式碼
query(1,1,dfn1[x])
AC_code
#include<bits/stdc++.h> using namespace std; typedef long long LL; const int N = 1e5 + 10,M = N*2; struct Node { int l,r; LL add,sum; }tr[N<<3]; struct DfsNode { int v,f; }dfspath[N<<1]; int h[N],e[M],ne[M],w[N],idx; int dfn1[N],dfn2[N],num[N<<1],ts; int n,m; void add(int a,int b) { e[idx] = b,ne[idx] = h[a],h[a] = idx++; } void dfs(int u,int fa) { dfn1[u] = ++ts,dfspath[ts].f = 1; dfspath[ts].v = u; for(int i=h[u];~i;i=ne[i]) { int j = e[i]; if(j==fa) continue; dfs(j,u); } dfn2[u] = ++ts,dfspath[ts].f = -1; dfspath[ts].v = u; } void pushup(int u) { tr[u].sum = tr[u<<1].sum + tr[u<<1|1].sum; } void pushdown(int u) { auto &root = tr[u],&left = tr[u<<1],&right = tr[u<<1|1]; if(root.add) { left.add += root.add; left.sum += (num[left.r] - num[left.l-1])*root.add; right.add += root.add; right.sum += (num[right.r] - num[right.l-1])*root.add; root.add = 0; } } void build(int u,int l,int r) { if(l==r) { tr[u] = {l,r,0,dfspath[l].f*w[dfspath[l].v]}; return ; } tr[u] = {l,r,0,0}; int mid = l + r >> 1; build(u<<1,l,mid),build(u<<1|1,mid+1,r); pushup(u); } void modify(int u,int l,int r,int k) { if(l<=tr[u].l&&tr[u].r<=r) { tr[u].add += k; tr[u].sum += 1ll*k*(num[tr[u].r]-num[tr[u].l-1]); return ; } pushdown(u); int mid = tr[u].l + tr[u].r >> 1; if(l<=mid) modify(u<<1,l,r,k); if(r>mid) modify(u<<1|1,l,r,k); pushup(u); } LL query(int u,int l,int r) { if(l<=tr[u].l&&tr[u].r<=r) return tr[u].sum; pushdown(u); int mid = tr[u].l + tr[u].r >> 1; LL res = 0; if(l<=mid) res += query(u<<1,l,r); if(r>mid) res += query(u<<1|1,l,r); pushup(u); return res; } int main() { scanf("%d%d",&n,&m); memset(h,-1,sizeof h); for(int i=1;i<=n;i++) scanf("%d",&w[i]); for(int i=0;i<n-1;i++) { int a,b;scanf("%d%d",&a,&b); add(a,b),add(b,a); } dfs(1,-1); for(int i=1;i<=ts;i++) num[i] = num[i-1] + dfspath[i].f; build(1,1,2*n); while(m--) { int op,x,c;scanf("%d%d",&op,&x); if(op==1) { scanf("%d",&c); modify(1,dfn1[x],dfn1[x],c); modify(1,dfn2[x],dfn2[x],c); } else if(op==2) { scanf("%d",&c); modify(1,dfn1[x],dfn2[x],c); } else cout<<query(1,1,dfn1[x])<<endl; } return 0; }