P4074 [WC2013]糖果公园 题解
这道题可以说是一道树上带修莫队的板子题。虽然评级是黑的,但是树上带修莫队本身还是比较好想的。就是代码很难调。
update 2021/4/12:现在评级掉紫了。
树上莫队:
树上莫队的本质就是利用欧拉序将树上莫队问题变成序列莫队问题。
我们设 \(\{eular\}\) 表示欧拉序序列, \(fir_i,las_i\) 表示节点 \(i\) 在欧拉序中第一次出现与第二次出现的位置。那么当询问为 \(x,y\) 时(此处我们规定 \(x\) 的深度小于 \(y\)),如果 \(lca(x,y)=x\) ,那么用 \(fir_x,fir_y\) 的区间,否则用 \(las_x,fir_y\) 的区间同时带上 \(lca(x,y)\) 的贡献。为什么不是 \(fir_x\) ?因为中间出现二次的节点我们是不考虑的,因此用 \(fir_x\) 相当于浪费时间 (用时间换空间的当我没说) 。这里千万注意:序列长度是 \(2n\) 而不是 \(n\) ,千万不要在这里 TLE 了!
带修莫队:
带修莫队的本质就是加一个时间轴,让指针除了在 \(l,r\) 上动还可以在 \(time\) 上动,将询问与修改分开储存,就可以完成了。
树上带修莫队:
实际上就是前面两个的结合体,先跑一遍欧拉序,然后再跑一遍带修莫队即可。
三个注意点:
- 在跑莫队的时候,如果要计算 \(lca(x,y)\) 的贡献(这里我们规定 \(lca(x,y)\) 不在询问的区间内,如果在可以前面直接先特判一下),算完之后一定不要忘记还原!
- 再次提醒:数列长度是 \(2n\) ,千万不能在这里 TLE 了!
- 这里块长取 \(eular^{\frac{2}{3}}\) ,比 \(\sqrt{eular}\) 要好一点,其中 \(eular\) 表示欧拉序的长度
几个小优化:
- 如果可以,使用树链剖分求 \(lca\)。
吸氧。(经过实测,我一开始调的块长是 \(n^{\frac{2}{3}}\),调错了,但是吸氧之后 \(30pts\) \(->\) \(70pts\))
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+5;
int n,m,que,v[MAXN],w[MAXN],c[MAXN],fir[MAXN],las[MAXN],ys[MAXN],block,fa[MAXN][21],eular[MAXN<<1],dep[MAXN],cntq,cntc,vis[MAXN];
typedef long long LL;
LL ans[MAXN],total,cnt[MAXN];
vector<int>Next[MAXN];
struct query
{
int l,r,id,lca,Time;
}q[MAXN];
struct change
{
int pos,val;
}cha[MAXN];
int read()
{
int sum=0;char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') {sum=(sum<<3)+(sum<<1)+(ch^48);ch=getchar();}
return sum;
}
void dfs(int x)
{
eular[++eular[0]]=x;
fir[x]=eular[0];
for(int i=0;i<Next[x].size();i++)
{
int u=Next[x][i];
if(dep[u]) continue;
dep[u]=dep[x]+1;
fa[u][0]=x;
dfs(u);
}
eular[++eular[0]]=x;
las[x]=eular[0];
}
void st()
{
for(int i=1;i<=20;i++)
for(int j=1;j<=n;j++)
fa[j][i]=fa[fa[j][i-1]][i-1];
}
int getlca(int x,int y)
{
if(dep[x]<dep[y]) swap(x,y);
int d=dep[x]-dep[y],tmp=-1;
while(d)
{
tmp++;int p=d&1;d>>=1;
if(p) x=fa[x][tmp];
}
if(x==y) return x;
for(int i=20;i>=0;i--)
if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
bool cmp(const query &fir,const query &sec)
{
if(ys[fir.l]^ys[sec.l]) return ys[fir.l]<ys[sec.l];
if(ys[fir.r]^ys[sec.r]) return ys[fir.r]<ys[sec.r];
return fir.Time<sec.Time;
}
void add(int x)
{
total+=1ll*v[c[x]]*w[++cnt[c[x]]];
}
void del(int x)
{
total-=1ll*v[c[x]]*w[cnt[c[x]]--];
}
void work(int x)
{
vis[x]?del(x):add(x);
vis[x]^=1;
}
void deal(int t)
{
if(vis[cha[t].pos])
{
work(cha[t].pos);
swap(c[cha[t].pos],cha[t].val);
work(cha[t].pos);
}
else swap(c[cha[t].pos],cha[t].val);
}
int main()
{
n=read();m=read();que=read();
for(int i=1;i<=m;i++) v[i]=read();
for(int i=1;i<=n;i++) w[i]=read();
for(int i=1;i<n;i++)
{
int x=read(),y=read();
Next[x].push_back(y);
Next[y].push_back(x);
}
for(int i=1;i<=n;i++) c[i]=read();
fa[1][0]=1;dep[1]=1;dfs(1);st();block=ceil(pow(eular[0],2.0/3.0));
for(int i=1;i<=(n<<1);i++) ys[i]=(i-1)/block+1;
for(int i=1;i<=que;i++)
{
int opt=read(),zzh1=read(),zzh2=read();
if(opt)
{
q[++cntq].id=cntq;
q[cntq].Time=cntc;
if(fir[zzh1]>fir[zzh2]) swap(zzh1,zzh2);
int qlca=getlca(zzh1,zzh2);
if(zzh1==qlca) q[cntq].l=fir[zzh1],q[cntq].r=fir[zzh2];
else q[cntq].l=las[zzh1],q[cntq].r=fir[zzh2],q[cntq].lca=qlca;
}
else
{
cha[++cntc].pos=zzh1;
cha[cntc].val=zzh2;
}
}
sort(q+1,q+cntq+1,cmp);
int l=1,r=0,t=0;
for(int i=1;i<=cntq;i++)
{
while(l<q[i].l) work(eular[l++]);
while(l>q[i].l) work(eular[--l]);
while(r<q[i].r) work(eular[++r]);
while(r>q[i].r) work(eular[r--]);
while(t<q[i].Time) deal(++t);
while(t>q[i].Time) deal(t--);
if(q[i].lca) work(q[i].lca);
ans[q[i].id]=total;
if(q[i].lca) work(q[i].lca);
}
for(int i=1;i<=cntq;i++) printf("%lld\n",ans[i]);
return 0;
}