本文版權歸ljh2000和博客園共有,歡迎轉載,但須保留此聲明,並給出原文鏈接,謝謝合作。
本文作者:ljh2000
作者博客:http://www.cnblogs.com/ljh2000-jump/
轉載請注明出處,侵權必究,保留最終解釋權!
【問題描述】
Candyland 有一座糖果公園,公園里不僅有美麗的風景、好玩的游樂項目,還有許多免費糖果的發放點,這引來了許多貪吃的小朋友來糖果公園游玩。
糖果公園的結構十分奇特,它由 n 個游覽點構成,每個游覽點都有一個糖果發放處,我們可以依次將游覽點編號為 1 至 n。有 n – 1 條 雙向道路 連接着這些游覽點,並且整個糖果公園都是 連通的 ,即從任何一個游覽點出發都可以通過這些道路到達公園里的所有其它游覽點。
糖果公園所發放的糖果種類非常豐富,總共有 m 種,它們的編號依次為 1至 m。每一個糖果發放處都只發放某種特定的糖果,我們用 C i 來表示 i 號游覽點的糖果。
來到公園里游玩的游客都 不喜歡走回頭路 ,他們總是從某個特定的游覽點出發前往另一個特定的游覽點,並游覽途中的景點,這條路線一定是唯一的。他們經過每個游覽點,都可以品嘗到一顆對應種類的糖果。
大家對不同類型糖果的喜愛程度都不盡相同。根據游客們的反饋打分,我們得到了糖果的美味指數,第 i 種糖果的美味指數為 V i 。另外,如果一位游客反復地品嘗同一種類的糖果,他肯定會覺得有一些膩。根據量化統計,我們得到了游客第 i 次品嘗某類糖果的新奇指數 W i 。如果一位游客第 i 次品嘗第 j 種糖果,那么他的愉悅指數 H 將會增加對應的美味指數與新奇指數的乘積,即 V j W i 。這位游客游覽公園的愉悅指數最終將是這些乘積的和。
當然,公園中每個糖果發放點所發放的糖果種類不一定是一成不變的。有時,一些糖果點所發放的糖果種類可能會更改(也只會是 m 種中的一種),這樣的目的是能夠讓游客們總是感受到驚喜。
糖果公園的工作人員小 A 接到了一個任務,那就是 根據公園最近的數據統計出每位游客游玩公園的愉悅指數 。但數學不好的小 A 一看到密密麻麻的數字就覺得頭暈,作為小 A 最好的朋友,你決定幫他一把。
【輸入文件】
從文件 park.in 中讀入數據。
第一行包含三個正整數 n, m, q,分別表示游覽點個數、糖果種類數和操作次數。
第二行包含 m 個正整數 V 1 , V 2 , ..., V m 。
第三行包含 n 個正整數 W 1 , W 2 , ..., W n 。
第四行到第 n + 2 行,每行包含兩個正整數 A i , B i ,表示這兩個游覽點之間有路徑可以直接到達。
第 n + 3 行包含 n 個正整數 C 1 , C 2 , ..., C n 。
接下來 q 行,每行包含三個整數 Type, x, y,表示一次操作:
若 Type 為 0,則 1 ≤ x ≤ n,1 ≤ y ≤ m,表示將編號為 x 的游覽點發放的糖果類型改為 y;
若 Type 為 1,則 1 ≤ x, y ≤ n,表示對出發點為 x,終止點為 y 的路線詢問愉悅指數。
【輸出文件】
輸出到文件 park.out 中。
按照輸入的先后順序,對於每個 Type 為 1 的操作輸出一行,用一個正整數
表示答案。
樣例一
input
4 3 5
1 9 2
7 6 5 1
2 3
3 1
3 4
1 2 3 2
1 1 2
1 4 2
0 2 1
1 1 2
1 4 2
output
84
131
27
84
正解:樹上帶修改莫隊
解題報告:
這道題算是樹上帶修改莫隊的經典題了。
我卡常數卡了好久都沒過,結果發現是我樹上分塊寫萎了......
首先這道題求的是$\sum^{m}_{i=1}$ ( $val[i]*$ $\sum^{Tim[i]}_{j=1}w[j]$)
$Tim[i]$表示顏色i的出現次數,$val[i]$表示顏色i的價值,$w[j]$表示第j次的新奇指數
對於一種顏色,顯然我們只關心一條路徑上有多少個即可,順序並不影響。
所以我們考慮帶修改莫隊的模式,按$(l/block,r/block,t)$排序,當然這道題並不是直接除以$block$,而是先在樹上分塊(分塊的方式同BZOJ1086王室聯邦),再按每個點所在的塊的編號來排序。
接着,我們就可以操作了。
修改操作顯然就是先消除原來顏色的影響,再加入新的顏色的影響,並且與序列帶修改莫隊相同的是,也是模擬時間流逝和倒流。
關鍵在於我們如何從這一次詢問轉移到下一次詢問。考慮我們樹上的所有結點,實際上可以認為是$01$狀態——計入答案或者未計入答案。
那么我就可以用類似於異或的思想來執行操作(不妨設為$change$操作),比如:計入答案再從答案中去掉,等於異或了兩次$1$,就等於原來的數。假設這次的起點、終點為$u$、$v$,上次為$x$、$y$,那么我可以對$x$到$u$的路徑、$v$到$y$的路徑執行$change$操作,直接查詢即可。
在紙上作圖就可以發現上述做法的妙處,可以大大避免大量兩次$change$操作變回自身的情況。$change$操作直接暴力做即可.
而為了方便,需要給每個結點一個標記,表示是否已經被計入答案中了(因為是否計入答案中顯然會產生不一樣的影響)。
附:復雜度證明:
設block_num為塊數,block_size為塊的大小,則有block_num×block_size=n,在證明中我們假設n,q同階。
設塊對(block_i,block_j),易知這樣的塊對不會超過block_size2個。
對於塊對內的操作:我們考慮總復雜度,左端點共移動至多O(q×block_size),右端點亦是。時間共移動至多O(block_num2×q)。故這一部分的復雜度為O(n×(block_size+block_num2))。
對於塊與塊之間的操作,不超過block_num2次:左端第移動一次,最多O(n),右端點亦是如此。時間最多移動O(q)=O(n)。故這一部分復雜度為O(block_num2×n)。
故總復雜度為O(n×(block_size+block_num2))。
可以證明當block_size=n2/3時,block_num=n1/3,復雜度最優,為O(n5/3)。
//It is made by ljh2000 #include <iostream> #include <cstdlib> #include <cstring> #include <cstdio> #include <cmath> #include <algorithm> using namespace std; typedef long long LL; const int MAXN = 100011; const int MAXM = 200011; int n,m,q,val[MAXN],w[MAXN],col[MAXN],pre[MAXN],belong[MAXN],stack[MAXN],top,Tim[MAXN],cc,vis[MAXN]; int ecnt,first[MAXN],to[MAXM],next[MAXM],cnt1,cnt2,block,deep[MAXN],f[MAXN][18],dfn[MAXN]; LL A[MAXN],ans; struct ask{int l,r,t,lb,rb,id;}a[MAXN]; struct UP{int x,y,pre;}b[MAXN]; inline bool cmp(ask q,ask qq){ if(q.lb==qq.lb && q.rb==qq.rb) return q.t<qq.t; if(q.lb==qq.lb) return q.rb<qq.rb; return q.lb<qq.lb; } inline void link(int x,int y){ next[++ecnt]=first[x]; first[x]=ecnt; to[ecnt]=y; } inline int getint(){ int w=0,q=0; char c=getchar(); while((c<'0'||c>'9') && c!='-') c=getchar(); if(c=='-') q=1,c=getchar(); while (c>='0'&&c<='9') w=w*10+c-'0',c=getchar(); return q?-w:w; } inline int lca(int x,int y){ if(deep[x]<deep[y]) swap(x,y); int t=0; while((1<<t)<=deep[x]) t++; t--; for(int i=t;i>=0;i--) if(deep[x]-(1<<i)>=deep[y]) x=f[x][i]; if(x==y) return y; for(int i=t;i>=0;i--) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; return f[x][0]; } inline int dfs(int x,int fa){ int remain=0; dfn[x]=++cc; for(int i=first[x];i;i=next[i]) { int v=to[i]; if(v==fa) continue; f[v][0]=x; deep[v]=deep[x]+1; remain+=dfs(v,x); if(remain>=block) { ecnt++; while(remain>0)/*!!!*/ belong[stack[top]]=ecnt,top--,remain--; remain=0; } } stack[++top]=x; return remain+1; } inline void update(int x){//改變x點的狀態 if(vis[x]) {//從答案中消除 vis[x]=0;//標記為未計入答案 ans-=(LL)w[ Tim[col[x]] ]*val[col[x]]; Tim[col[x]]--; } else{//從答案中加入 vis[x]=1; Tim[col[x]]++; ans+=(LL)w[ Tim[col[x]] ]*val[col[x]];//!!! } } inline void change(int x,int y){ while(x!=y) { if(deep[x]<deep[y]) update(y),y=f[y][0]; else update(x),x=f[x][0]; } } inline void modify(int x,int C){//把x修改為顏色C if(!vis[x]) col[x]=C; else { update(x); col[x]=C; update(x); } } inline void work(){ n=getint(); m=getint(); q=getint(); int x,y; block=(int)pow(n,0.60); for(int i=1;i<=m;i++) val[i]=getint(); for(int i=1;i<=n;i++) w[i]=getint(); for(int i=1;i<n;i++) { x=getint(); y=getint(); link(x,y); link(y,x); } for(int i=1;i<=n;i++) col[i]=getint(),pre[i]=col[i]; ecnt=0; deep[1]=1; dfs(1,0); int ljh,LCA; for(int j=1;j<=17;j++) for(int i=1;i<=n;i++) f[i][j]=f[f[i][j-1]][j-1]; for(int o=1;o<=q;o++) { ljh=getint(); x=getint(); y=getint(); if(ljh==0) { b[++cnt2].pre=pre[x]; b[cnt2].x=x; b[cnt2].y=y; pre[x]=y;/*!!!*/ } else{ if(dfn[x]>dfn[y]) swap(x,y); a[++cnt1].l=x; a[cnt1].r=y; a[cnt1].lb=belong[x]; a[cnt1].rb=belong[y]; a[cnt1].t=cnt2; a[cnt1].id=cnt1; } } sort(a+1,a+cnt1+1,cmp); cnt2=a[1].t; for(int i=1;i<=a[1].t;i++) modify(b[i].x,b[i].y); change(a[1].l,a[1].r); LCA=lca(a[1].l,a[1].r); update(LCA);//lca並未處理 A[a[1].id]=ans; update(LCA); for(int i=2;i<=cnt1;i++) { while(cnt2<a[i].t) cnt2++,modify(b[cnt2].x,b[cnt2].y); while(cnt2>a[i].t) modify(b[cnt2].x,b[cnt2].pre),cnt2--; change(a[i-1].l,a[i].l); change(a[i-1].r,a[i].r); LCA=lca(a[i].l,a[i].r); update(LCA); A[a[i].id]=ans; update(LCA); } for(int i=1;i<=cnt1;i++) printf("%lld\n",A[i]); } int main() { work(); return 0; }