普通的莫队只能处理链上问题,那么如果我们想在一棵树上跑莫队的话,我们就要将这颗树压成链状。这里我们引入一种处理树的方法——就是将其变成括号序列
namo什么是括号序列呢?让我们对树进行dfs,当从这个点开始dfs的时候,我们往序列中加入这个点的编号,当这个点的dfs结束时,我们也往序列中加入这个点的编号,那么任意一个点在括号序列中均出现了两次,就像左括号和右括号那样,所以这样的序列被叫做了括号序列。
考虑这颗树的括号序列
很明显假设我们从1开始dfs的话,这颗树的括号序列就是 1–2–4–4–2–3–5–5–3–1
让我们把所有点第一次加入括号序列时的编号放入in数组中,第二次出现时放入out数组中,那么很显然我们的in数组=[1, 2, 6, 3, 7],我们的out数组=[10, 5, 9, 4, 8]。
得到了括号序列和in、out数组之后,我们就可以开始考虑这种情况下左,右指针的移动情况。
假设我们要统计节点1到节点4的信息,那么我们可以直接遍历{ i n 1 in_1 in1, i n 4 in_4 in4}={1,2,3}。
假设我们要统计节点1到节点5的信息,那么我们可以直接编列{ i n 1 in_1 in1, i n 5 in_5 in5}={1,2,3,4,5,6,7}。此时我们可以发现 i n 2 in_2 in2和 o u t 2 out_2 out2、 i n 4 in_4 in4和 o u t 4 out_4 out4均出现了两次,也就是说节点2和节点4都被便利了两次,但其实这两个节点的贡献是0,所以我们要注意消除这个影响。
假设我们要遍历节点2到节点3的信息,那么我们可以直接便利{ o u t 2 out_2 out2, i n 3 in_3 in3}={5,6},此时我们发现了,它没有遍历到lca(2,3)也就是节点1的信息,此时我们就要把节点1的信息加上了,
我们可以得出一个结论:
若( lca(x,y) == x || lca(x,y) == y ) 那么我们就遍历{
i
n
x
in_x
inx,
i
n
y
in_y
iny},并且消除遍历多次节点的影响。
若(( lca(x,y) ! = x && lca(x,y) ! = y )) 那么我们就遍历{
o
u
t
x
out_x
outx,
i
n
y
in_y
iny},并且要加上lca(x,y)的贡献
这里我们用一道例题来解释如何的消除这些影响糖果公园
我们可以用vis数组来标记每一个点,当这个点被访问奇数次的时候,我们就要加上这个节点的贡献,否则,我们就要减去这个节点的贡献,这里我们可以用vis[i]^=1来达到目的。
当然,在这个过程中,我们也需要求某两个节点的lca,这里用倍增和树剖求lca都是可以接受的。另外这个题还需要修改操作,那么我们只需要在莫队的基础上添加一个维度time,就可以完成修改操作了
参考代码
#include<bits/stdc++.h>
#define endl "\n"
#define x first
#define y second
#define Endl endl
typedef long long ll;
const int INF=0x7fffffff;
const double PI=3.14159265359;
using namespace std;
const int N=4e5+10;
int h[N],ne[N],e[N],idx;
int son[N],sz[N],deep[N],fa[N],top[N],dfn,belong[N],v[N],w[N],last[N],cnt,vis[N],times[N],id[N],c[N];
ll ans[N];
int in[N],out[N];
int n,m,q;
ll sum;
struct point{
int x,y,lca,id,time;
}s1[N];
struct point2{
int pos,now,pre;
}s2[N];
bool cmp(point a,point b){
if(belong[a.x]!=belong[b.x]) return belong[a.x]<belong[b.x];
else {
if(belong[a.y]==belong[b.y]) return a.time<b.time;
else return a.y<b.y;
}
}
void add(int a,int b){
ne[idx]=h[a],e[idx]=b,h[a]=idx++;
}
void dfs1(int x,int dep,int father){
sz[x]=1;
deep[x]=dep;
fa[x]=father;
for(int i=h[x];~i;i=ne[i]){
int j=e[i];
if(j!=father){
dfs1(j,dep+1,x);
sz[x]+=sz[j];
if(!son[x]||sz[son[x]]<sz[j]) son[x]=j;//树剖部分
}
}
}
void dfs2(int x,int tp){
in[x]=++cnt;
id[cnt]=x;
top[x]=tp;
if(son[x]) dfs2(son[x],tp);
for(int i=h[x];~i;i=ne[i]){
int j=e[i];
if(j!=fa[x]&&j!=son[x]){//判断轻链
dfs2(j,j);
}
}
out[x]=++cnt;
id[cnt]=x;
}
int lca(int x,int y){//树剖求lca
while(top[x]!=top[y]){
if(deep[top[x]]<deep[top[y]]) swap(x,y);
x=fa[top[x]];
}
return deep[x]<deep[y]?x:y;
}
void cal(int pos){
if(vis[pos]){
sum-=(ll)v[c[pos]]*(w[times[c[pos]]]);
times[c[pos]]--;
}
else{
times[c[pos]]++;
sum+=(ll)v[c[pos]]*(w[times[c[pos]]]);
}
vis[pos]^=1;
}
void change(int pos,int x){
if(vis[pos]){
cal(pos);
c[pos]=x;
cal(pos);
}
else c[pos]=x;
}
int main(){
memset(h,-1,sizeof h);
idx=0;
cin>>n>>m>>q;
for(int i=1;i<=m;i++){
cin>>v[i];//美味指数
}
for(int i=1;i<=n;i++){
cin>>w[i];//新奇指数
}
for(int i=1;i<n;i++){
int a,b;
cin>>a>>b;
add(a,b);
add(b,a);
}
for(int i=1;i<=n;i++){
cin>>c[i];
last[i]=c[i];
}
dfs1(1,1,1);
dfs2(1,1);
int cnt1=0,cnt2=0;
for(int i=1;i<=q;i++){
int opt,x,y;
cin>>opt>>x>>y;
if(opt==1){
int k=lca(x,y);
if(in[x]>in[y]) swap(x,y);
if(k==x){
s1[++cnt1].x=in[x],s1[cnt1].y=in[y],s1[cnt1].lca=0,s1[cnt1].time=cnt2,s1[cnt1].id=cnt1;
}
else{
s1[++cnt1].x=out[x],s1[cnt1].y=in[y],s1[cnt1].lca=k,s1[cnt1].time=cnt2,s1[cnt1].id=cnt1;
}
}
else{
s2[++cnt2]={x,y,last[x]};
last[x]=y;
}
}
int block=pow(double(cnt),2.0/3);//带修莫队的最优块长
for(int i=1;i<=cnt;i++){
belong[i]=(i-1)/block+1;
}
sort(s1+1,s1+1+cnt1,cmp);
int L=1,R=1;
cal(id[1]);
for(int i=1;i<=cnt1;i++){
for(int j=s1[i-1].time+1;j<=s1[i].time;j++){
change(s2[j].pos,s2[j].now);
}
for(int j=s1[i-1].time;j>s1[i].time;j--){
change(s2[j].pos,s2[j].pre);
}
int l=s1[i].x,r=s1[i].y;
while(l<L) cal(id[--L]);
while(l>L) cal(id[L++]);
while(r<R) cal(id[R--]);
while(r>R) cal(id[++R]);
if(s1[i].lca){
cal(s1[i].lca);
ans[s1[i].id]=sum;
cal(s1[i].lca);
}
else{
ans[s1[i].id]=sum;
}
}
for(int i=1;i<=cnt1;i++){
cout<<ans[i]<<endl;
}
}