P4074 [WC2013]糖果公园 题解

这道题可以说是一道树上带修莫队的板子题。虽然评级是黑的,但是树上带修莫队本身还是比较好想的。就是代码很难调。

update 2021/4/12:现在评级掉紫了。

树上莫队:

树上莫队的本质就是利用欧拉序将树上莫队问题变成序列莫队问题。

我们设 { e u l a r } \{eular\} {eular} 表示欧拉序序列, f i r i , l a s i fir_i,las_i firi,lasi 表示节点 i i i 在欧拉序中第一次出现与第二次出现的位置。那么当询问为 x , y x,y x,y 时(此处我们规定 x x x 的深度小于 y y y),如果 l c a ( x , y ) = x lca(x,y)=x lca(x,y)=x ,那么用 f i r x , f i r y fir_x,fir_y firx,firy 的区间,否则用 l a s x , f i r y las_x,fir_y lasx,firy 的区间同时带上 l c a ( x , y ) lca(x,y) lca(x,y) 的贡献。为什么不是 f i r x fir_x firx ?因为中间出现二次的节点我们是不考虑的,因此用 f i r x fir_x firx 相当于浪费时间 (用时间换空间的当我没说) 。这里千万注意:序列长度是 2 n 2n 2n 而不是 n n n ,千万不要在这里 TLE 了!

带修莫队:

带修莫队的本质就是加一个时间轴,让指针除了在 l , r l,r l,r 上动还可以在 t i m e time time 上动,将询问与修改分开储存,就可以完成了。

树上带修莫队:

实际上就是前面两个的结合体,先跑一遍欧拉序,然后再跑一遍带修莫队即可。

三个注意点:

  1. 在跑莫队的时候,如果要计算 l c a ( x , y ) lca(x,y) lca(x,y) 的贡献(这里我们规定 l c a ( x , y ) lca(x,y) lca(x,y) 不在询问的区间内,如果在可以前面直接先特判一下),算完之后一定不要忘记还原
  2. 再次提醒:数列长度是 2 n 2n 2n ,千万不能在这里 TLE 了!
  3. 这里块长取 e u l a r 2 3 eular^{\frac{2}{3}} eular32 ,比 e u l a r \sqrt{eular} eular 要好一点,其中 e u l a r eular eular 表示欧拉序的长度

几个小优化:

  1. 如果可以,使用树链剖分求 l c a lca lca
  2. 吸氧。(经过实测,我一开始调的块长是 n 2 3 n^{\frac{2}{3}} n32,调错了,但是吸氧之后 30 p t s 30pts 30pts − > -> > 70 p t s 70pts 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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值