Problem: Given a tree, the color of node \(i\) is \(c_i\). Each query asks for a path \(u_i\), \(v_i\), and requires the value:
\(\sum_{c}val_c\sum_{i=1}^{cnt_c}w_i\)
where: \(val\) represents the value of the color, \(cnt\) represents the number of occurrences of the color, and \(w\) represents the value when the color appears \(i\) times.
Process
First, transform the tree into a sequence. Then, each time we add/remove a point, the contribution of this point to the answer can be obtained in \(O(1)\) time, i.e., \(val_c\times w_{cnt_{c+1}}\).
We notice that because the subtree of the starting point is also traversed, it produces redundant contributions. How to handle this?
Since points in the subtree of the starting point will be traversed twice during the process, but their contribution is 0.
So we can maintain a \(vis\) array. Each time we traverse point x, we toggle \(vis_x\) by XOR with 1.
If \(vis_x=0\), the contribution of this point can be ignored.
Thus, we can use Mo's algorithm on tree to solve this.
To handle modifications, add a time dimension, turning it into Mo's algorithm on tree with modifications.
Then, because the included interval might not contain the LCA, for such cases we need to remove the extra contribution, and then we are done.
#include<algorithm>#include<cmath>#include<cstdio>usingnamespacestd;constexprintMAXN=200010;intf[MAXN],g[MAXN],id[MAXN],head[MAXN],cnt,last[MAXN],dep[MAXN],fa[MAXN][22],v[MAXN],w[MAXN];intblock,index,n,m,q;intpos[MAXN],col[MAXN],app[MAXN];boolvis[MAXN];longlongans[MAXN],cur;structedge{intto,nxt;}e[MAXN];intcnt1=0,cnt2=0;// timestampsstructquery{intl,r,t,id;booloperator<(constquery&b)const{return(pos[l]<pos[b.l])||(pos[l]==pos[b.l]&&pos[r]<pos[b.r])||(pos[l]==pos[b.l]&&pos[r]==pos[b.r]&&t<b.t);}}a[MAXN],b[MAXN];voidaddedge(intx,inty){e[++cnt]=edge{y,head[x]};head[x]=cnt;}voiddfs(intx){id[f[x]=++index]=x;for(inti=head[x];i;i=e[i].nxt){if(e[i].to!=fa[x][0]){fa[e[i].to][0]=x;dep[e[i].to]=dep[x]+1;dfs(e[i].to);}}id[g[x]=++index]=x;// Euler tour order}intlca(intx,inty){if(dep[x]<dep[y])swap(x,y);if(dep[x]!=dep[y]){// climb to same depthintdis=dep[x]-dep[y];for(inti=20;i>=0;i--)if(dis>=(1<<i))dis-=1<<i,x=fa[x][i];}if(x==y)returnx;for(inti=20;i>=0;i--){if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];}returnfa[x][0];}voidadd(intx){if(vis[x])cur-=(longlong)v[col[x]]*w[app[col[x]]--];elsecur+=(longlong)v[col[x]]*w[++app[col[x]]];vis[x]^=1;}// move along the time dimensionvoidmodify(intx,intt){if(vis[x]){add(x);col[x]=t;add(x);}elsecol[x]=t;}intmain(){scanf("%d%d%d",&n,&m,&q);for(inti=1;i<=m;i++)scanf("%d",&v[i]);for(inti=1;i<=n;i++)scanf("%d",&w[i]);for(inti=1;i<n;i++){intx,y;scanf("%d%d",&x,&y);addedge(x,y);addedge(y,x);}for(inti=1;i<=n;i++){scanf("%d",&last[i]);col[i]=last[i];}dfs(1);for(intj=1;j<=20;j++)for(inti=1;i<=n;i++)fa[i][j]=fa[fa[i][j-1]][j-1];// preprocess ancestorsintblock=pow(index,2.0/3);for(inti=1;i<=index;i++){pos[i]=(i-1)/block;}while(q--){intopt,x,y;scanf("%d%d%d",&opt,&x,&y);if(opt==0){b[++cnt2].l=x;b[cnt2].r=last[x];last[x]=b[cnt2].t=y;}else{if(f[x]>f[y])swap(x,y);a[++cnt1]=query{lca(x,y)==x?f[x]:g[x],f[y],cnt2,cnt1};}}sort(a+1,a+cnt1+1);intL,R,T;// pointer coordinatesL=R=0;T=1;for(inti=1;i<=cnt1;i++){while(T<=a[i].t){modify(b[T].l,b[T].t);T++;}while(T>a[i].t){modify(b[T].l,b[T].r);T--;}while(L>a[i].l){L--;add(id[L]);}while(L<a[i].l){add(id[L]);L++;}while(R>a[i].r){add(id[R]);R--;}while(R<a[i].r){R++;add(id[R]);}intx=id[L],y=id[R];intllca=lca(x,y);if(x!=llca&&y!=llca){add(llca);ans[a[i].id]=cur;add(llca);}elseans[a[i].id]=cur;}for(inti=1;i<=cnt1;i++){printf("%lld\n",ans[i]);}return0;}
True Mo's Algorithm on Tree
The above Mo's algorithm on tree only transforms the tree into a chain. The following is the true Mo's algorithm on tree.
Since Mo's algorithm related problems are mostly template problems, the implementation part will not be explained in too much detail.
Sorting Queries
First, we know that Mo's algorithm is based on block partitioning, so we need to find a tree partitioning method to guarantee time complexity.
Conditions:
The distance between nodes belonging to the same block should not exceed the given block size.
The number of nodes in each block should not be too many or too few.
Every node must belong to a block.
The distance between adjacent blocks (by ID) should not be too large.
Based on this problem, we only need to ensure the last condition to solve the partitioning problem.
Idea
Let lim be the desired block size. First, perform a DFS on the entire tree. When the size of a subtree exceeds lim, partition them into a block. It's easy to think that for the root, some nodes might remain, so these nodes are placed into the last block.
Method: Use a stack to maintain the current node as the parent visiting its children. When the distance from the top of the stack to the parent node exceeds the desired block size, pop these elements and form a block. The remaining nodes form a separate block.
Final sorting method: If the first timestamp is greater than the second, swap them. Sort by the block of the first dimension as the first key, and the timestamp of the second dimension as the second key.
Pointer Movement
Process
It's easy to think that we can mark the points included in the answer, and move the pointers directly towards the target, while toggling the points along the path.
However, there is a problem: if both pointers start at x, obviously x is marked. When both pointers move to the same child node (and many other situations), x should not be marked, but in reality x is marked because both pointers toggled it once, canceling each other out.
How to solve this?
There is an obvious property: these points must be some LCA, because only at the LCA could repeated toggling cause failure.
So we do not mark the LCA each time. When we need to query the answer, we mark the LCA, and then unmark it afterwards.
Implementation
1234567
// toggle all nodes on the path except the LCAvoidmove(intx,inty){if(dp[x]<dp[y])swap(x,y);while(dp[x]>dp[y])update(x),x=fa[x];while(x!=y)update(x),update(y),x=fa[x],y=fa[y];// x!=y ensures LCA is not toggled}
For finding LCA, we can use heavy-light decomposition (HLD). Then we can place the partitioning step inside the first DFS of HLD, and the timestamp can directly use the DFS order from the second DFS.
intbl[100002],bls=0;// block belonging, number of blocksunsignedstep;// block sizeintfa[100002],dp[100002],hs[100002]={0},sz[100002]={0};// parent, depth, heavy child, sizestack<int>sta;voiddfs1(intx){sz[x]=1;unsignedss=sta.size();for(inti=head[x];i;i=nxt[i])if(ver[i]!=fa[x]){fa[ver[i]]=x;dp[ver[i]]=dp[x]+1;dfs1(ver[i]);sz[x]+=sz[ver[i]];if(sz[ver[i]]>sz[hs[x]])hs[x]=ver[i];if(sta.size()-ss>=step){bls++;while(sta.size()!=ss)bl[sta.top()]=bls,sta.pop();}}sta.push(x);}// mainif(!sta.empty()){bls++;// this line is optionalwhile(!sta.empty())bl[sta.top()]=bls,sta.pop();}
Time Complexity
Here comes the key point, which relates to the choice of block size.
Let the block size be \(unit\):
For the x pointer, since the distance between nodes in each block is around \(unit\), the x pointer moves \(unit^2\) times per block (\(unit\times dis_{\max}\)), totaling \(n\times unit\) times (\(unit^2 \times (\frac{n}{unit})\));
For the y pointer, it moves at most \(O(n)\) times per block, totaling \(\frac{n^2}{unit}\) times (\(n \times (\frac{n}{unit})\)).
Adding them up, the minimum is roughly achieved at the square root (since the block size for Mo's algorithm on tree is not fixed, it doesn't have to strictly follow this).
Example "WC2013" Candy Park
Due to the additional time dimension, a block size of around \(n^{0.6}\) is appropriate.