1. 程式人生 > 實用技巧 >[ZJOI2008] 樹的統計Count

[ZJOI2008] 樹的統計Count

題目

Description

一棵樹上有n個節點,編號分別為1n,每個節點都有一個權值w。

我們將以下面的形式來要求你對這棵樹完成一些操作:

I. CHANGE u t:把結點u的權值改為t。

II. QMAX u v: 詢問從點u到點v的路徑上的節點的最大權值。

III. QSUM u v: 詢問從點u到點v的路徑上的節點的權值和。

注意:從點u到點v的路徑上的節點包括u和v本身。

Input

輸入檔案的第一行為一個整數n,表示節點的個數。

接下來n-1行,每行2個整數a和b,表示節點aa和節點bb之間有一條邊相連。

接下來一行n個整數,第i個整數wi​表示節點i的權值。

接下來1行,為一個整數q,表示操作的總數。

接下來q行,每行一個操作,以CHANGE u t或者QMAX u v或者QSUM u v的形式給出。

Output

對於每個QMAX或者QSUM的操作,每行輸出一個整數表示要求輸出的結果。

Sample Input

4
1 2
2 3
4 1
4 2 1 3
12
QMAX 3 4
QMAX 3 3
QMAX 3 2
QMAX 2 3
QSUM 3 4
QSUM 2 1
CHANGE 1 5
QMAX 3 4
CHANGE 3 6
QMAX 3 4
QMAX 2 4
QSUM 3 4

Sample Output

4
1
2
2
10
6
5
6
5
16

思路

這是一道對於剛學樹鏈剖分的讀者們,的很好的鍛鍊題;

有些不懂的讀者可以看看

樹鏈剖分入門(淺談樹鏈剖分)

沒什麼思路技巧,就是將樹分成若干條鏈,然後將這些鏈儲存線上段樹中;

首先要將樹,分成重邊和輕邊,再將連續的重邊組成重鏈,連續的輕邊組成輕鏈;

再將這些鏈上的點,附上不同的編號,放線上段樹中;

那麼還是具體上程式碼吧,程式碼註釋會將得很清楚的

程式碼

#pragma GCC optimize(3)
#pragma GCC target("avx")
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma
GCC optimize("-fgcse") #pragma GCC optimize("-fgcse-lm") #pragma GCC optimize("-fipa-sra") #pragma GCC optimize("-ftree-pre") #pragma GCC optimize("-ftree-vrp") #pragma GCC optimize("-fpeephole2") #pragma GCC optimize("-ffast-math") #pragma GCC optimize("-fsched-spec") #pragma GCC optimize("unroll-loops") #pragma GCC optimize("-falign-jumps") #pragma GCC optimize("-falign-loops") #pragma GCC optimize("-falign-labels") #pragma GCC optimize("-fdevirtualize") #pragma GCC optimize("-fcaller-saves") #pragma GCC optimize("-fcrossjumping") #pragma GCC optimize("-fthread-jumps") #pragma GCC optimize("-funroll-loops") #pragma GCC optimize("-fwhole-program") #pragma GCC optimize("-freorder-blocks") #pragma GCC optimize("-fschedule-insns") #pragma GCC optimize("inline-functions") #pragma GCC optimize("-ftree-tail-merge") #pragma GCC optimize("-fschedule-insns2") #pragma GCC optimize("-fstrict-aliasing") #pragma GCC optimize("-fstrict-overflow") #pragma GCC optimize("-falign-functions") #pragma GCC optimize("-fcse-skip-blocks") #pragma GCC optimize("-fcse-follow-jumps") #pragma GCC optimize("-fsched-interblock") #pragma GCC optimize("-fpartial-inlining") #pragma GCC optimize("no-stack-protector") #pragma GCC optimize("-freorder-functions") #pragma GCC optimize("-findirect-inlining") #pragma GCC optimize("-fhoist-adjacent-loads") #pragma GCC optimize("-frerun-cse-after-loop") #pragma GCC optimize("inline-small-functions") #pragma GCC optimize("-finline-small-functions") #pragma GCC optimize("-ftree-switch-conversion") #pragma GCC optimize("-foptimize-sibling-calls") #pragma GCC optimize("-fexpensive-optimizations") #pragma GCC optimize("-funsafe-loop-optimizations") #pragma GCC optimize("inline-functions-called-once") #pragma GCC optimize("-fdelete-null-pointer-checks") #pragma GCC optimize(2) //為什麼要加這麼一大堆,我只是為了讓看起來冗長的程式碼,變得更長 #include<bits/stdc++.h>//標頭檔案 #define re register//巨集定義 typedef long long ll; using namespace std; inline ll read()//快讀 { ll a=0,f=1; char c=getchar();//a 是數字大小,f 是判正負 //???為什麼快讀也要寫註釋 while (c<'0'||c>'9') {if (c=='-') f=-1; c=getchar();} while (c>='0'&&c<='9') {a=a*10+c-'0'; c=getchar();} return a*f; } ll n,m; ll w[200010];//w 記錄權值 ll head[200010]; ll size[200010],dep[200010],top[200010]; //size 記錄子樹 節點個數 ,dep 記錄深度, top 記錄這條鏈的頂部 ll id[200010],aa[200010];//id 是線上段樹中的編號,aa 是線上段樹中的權值 ll f[200010],son[200010];// f 是父節點,son 是重子節點 struct ljj { ll to,stb; }e[200010];//to 表示這條邊到達的點,stb 表示上一條邊 struct ljq { ll l,r,mx,v; }a[200010];//線段樹基本變數 inline ll L(ll x) { return 2*x; }//線段樹中左兒子的編號 inline ll R(ll x) { return 2*x+1; }//線段樹中右兒子的編號 ll s=0; inline void insert(ll x,ll y) { s++; e[s].stb=head[x]; e[s].to=y; head[x]=s; }//前向星連邊 inline void dfs(ll x,ll fa)//找重子節點 { size[x]=1; f[x]=fa;//記錄父節點 for(re ll i=head[x];i;i=e[i].stb) { ll xx=e[i].to; if(xx==fa)//不能遍歷到父節點 continue; dep[xx]=dep[x]+1;//統計深度 dfs(xx,x); size[x]+=size[xx];//統計子樹節點數 if(!son[x]||size[xx]>size[son[x]]) son[x]=xx;//找重子節點,也就是子樹節點數最多的子節點 } } ll tot=0;//統計線上段樹中的編號 inline void DFS(ll x,ll t)//t 表示這條鏈的頂部 { top[x]=t;//記錄 id[x]=++tot;//記錄線上段樹中的編號 aa[tot]=w[x];//記錄線上段樹中的權值 if(!son[x])//如果沒有重子節點 return;//返回 DFS(son[x],t);//先遍歷重子節點 for(re ll i=head[x];i;i=e[i].stb) { ll xx=e[i].to; if(xx==f[x]||xx==son[x])//遍歷輕子節點 continue; DFS(xx,xx);//每個開始的輕子節點的鏈頂就是自己 } } inline void doit(ll p)//維護區間 { a[p].v=a[L(p)].v+a[R(p)].v;//sum和 a[p].mx=max(a[L(p)].mx,a[R(p)].mx);//最大值 } inline void build(ll p,ll l,ll r)//建樹 { a[p].l=l; a[p].r=r; if(l==r) { a[p].v=aa[l]; a[p].mx=aa[l]; return; } ll mid=(l+r)>>1; build(L(p),l,mid); build(R(p),mid+1,r); doit(p); } inline void change(ll p,ll x,ll y)//單點修改 { if(a[p].l==a[p].r) { a[p].v=y; a[p].mx=y; return; } ll mid=(a[p].l+a[p].r)>>1; if(x<=mid) change(L(p),x,y); else change(R(p),x,y); doit(p); } inline ll findsum(ll p,ll l,ll r)//找區間sum { if(l<=a[p].l&&a[p].r<=r) return a[p].v; ll sum=0; ll mid=(a[p].l+a[p].r)>>1; if(l<=mid) sum+=findsum(L(p),l,r); if(r>mid) sum+=findsum(R(p),l,r); return sum; } inline ll qsum(ll x,ll xx) { ll sum=0; while(top[x]!=top[xx])//我們需要是 x 節點跳到與 xx 節點在同一條鏈上 { if(dep[top[x]]<dep[top[xx]])//深度大的往上跳 swap(x,xx); sum+=findsum(1,id[top[x]],id[x]);//統計 x 到鏈頂的 sum x=f[top[x]];// 跳到下一個區間 } if(dep[x]<dep[xx]) swap(x,xx); sum+=findsum(1,id[xx],id[x]);//在統計下 x 到 xx 的區間sum //此時 x 與 xx 是在同一條鏈上 return sum; } inline ll findmax(ll p,ll l,ll r)//區間最大值 { if(l<=a[p].l&&a[p].r<=r) return a[p].mx; ll sum=-(1<<30); ll mid=(a[p].l+a[p].r)>>1; if(l<=mid) sum=max(sum,findmax(L(p),l,r)); if(r>mid) sum=max(sum,findmax(R(p),l,r)); return sum; } inline ll qmax(ll x,ll xx) { ll sum=-(1<<30); while(top[x]!=top[xx])//我們需要是 x 節點跳到與 xx 節點在同一條鏈上 { if(dep[top[x]]<dep[top[xx]])//深度大的往上跳 swap(x,xx); sum=max(sum,findmax(1,id[top[x]],id[x]));//統計 x 到鏈頂的 最大值 x=f[top[x]];// 跳到下一個區間 } if(dep[x]<dep[xx]) swap(x,xx); sum=max(sum,findmax(1,id[xx],id[x]));//在統計下 x 到 xx 的區間最大值 return sum; } int main() { n=read();//讀入 for(re ll i=1;i<n;i++) { ll x=read(),y=read(); insert(x,y); insert(y,x);//連邊 } for(re ll i=1;i<=n;i++) w[i]=read();//讀入 dfs(1,0);//找重子節點 DFS(1,1);//分成鏈 build(1,1,n);//建樹 m=read(); for(re ll i=1;i<=m;i++) { char c[5]; scanf("%s",c); if(c[1]=='H') { ll x=read(),y=read(); change(1,id[x],y);//單點修改 } else if(c[1]=='S') { ll x=read(),y=read(); ll ans=qsum(x,y);//求區間sum printf("%lld\n",ans); } else { ll x=read(),y=read(); ll ans=qmax(x,y);//求區間最大值 printf("%lld\n",ans); } } //return 0; }