1. 程式人生 > 實用技巧 >P4074 [WC2013]糖果公園

P4074 [WC2013]糖果公園

P4074 [WC2013]糖果公園

Tag:樹上帶修莫隊

題意:樹上每個點有一種糖果,求\(\sum_c\sum_{i=1}^{cnt_c}v_c*w_i\)

其中c為糖果種類,\(cnt_c\)其為出現次數。

思路

離線樹上帶修莫隊。

先進行樹上分塊。分塊內的詢問按照出發點、終止點、詢問id優先順序依次遞減排序。

對於樹上莫隊,其實就是在尤拉序上莫隊。因為尤拉序的性質,即每個節點子樹內的節點一定會經過兩次,我們就可以用一個括號序列的方式在莫隊時消除子樹內無用節點的影響。

具體來說,序列長度為2*n,每一個節點出入隊時我們異或它是否在隊中即可。也就是記錄每個點出隊和入隊的時間戳,然後在序列上修改。

注意,對於LCA其實在尤拉序時是沒有包括的,所以我們需要單獨求一下LCA的影響。但是如果一個端點本身就是LCA就不用啦。

但是!用指標實現也太low了,我們直接利用它的樹形結構莫隊就行(實際上就是我看錯了題解)

程式碼

分塊和排序

	int dfs(int x,int f){//毒瘤的樹分塊而非序列分塊
		int siz=0;
		fa[x][0]=f;dep[x]=dep[f]+1;
		dfn[x]=++tim;
		for(int i=0;i<=20;i++)fa[x][i+1]=fa[fa[x][i]][i];
		for(int i=head[x];i;i=nxt[i]){
			int u=to[i];
			if(u==f)continue;
			siz+=dfs(u,x);
			if(siz>=_nsiz){
				_n++;
				while(siz)belong[st[top--]]=_n,siz--;
			}
		}
		st[++top]=x;
		return siz+1;
	}
//----------------
	++_n;
	while(top)belong[st[top--]]=_n;
inline bool operator < (const query&zp)const {return belong[u]<belong[zp.u] or (belong[u]==belong[zp.u] and (belong[v]<belong[zp.v] or (belong[v]==belong[zp.v] and id<zp.id)));}//運算子版

莫隊

	inline void reverse(const int& x,ll &ans){//將進隊的出隊,出隊的進隊。
		if(vis[x])ans-=1ll*w[num[color[x]]]*v[color[x]],num[color[x]]--;
		else num[color[x]]++,ans+=1ll*w[num[color[x]]]*v[color[x]];
		vis[x]^=1;
	}
	inline void solve(int x,int y,ll &ans){//直接利用樹形結構跳father,更新答案,反正括號序列也就是翻轉。
		while(x!=y){
			if(dep[x]>dep[y])reverse(x,ans),x=fa[x][0];
			else reverse(y,ans),y=fa[y][0];
		}
	}
		for(int i=1;i<=cntq;i++){
			while(now<cntm and mo[now+1].id<=q[i].id){//處理時間問題,只能暴力消除影響。
				now++;
				if(vis[mo[now].pos])ans-=w[num[color[mo[now].pos]]]*v[color[mo[now].pos]],num[color[mo[now].pos]]--;
				color[mo[now].pos]=mo[now].aft;
				if(vis[mo[now].pos])num[color[mo[now].pos]]++,ans+=1LL*w[num[color[mo[now].pos]]]*v[color[mo[now].pos]];
			}
			while(now>=1 and mo[now].id>=q[i].id){
				if(vis[mo[now].pos])ans-=w[num[color[mo[now].pos]]]*v[color[mo[now].pos]],num[color[mo[now].pos]]--;
				color[mo[now].pos]=mo[now].bef;//注意變成了什麼(我就是傻
				if(vis[mo[now].pos])num[color[mo[now].pos]]++,ans+=1LL*w[num[color[mo[now].pos]]]*v[color[mo[now].pos]];
				now--;
			}
			if(i==1)solve(q[i].u,q[i].v,ans);
			else solve(q[i].u,q[i-1].u,ans),solve(q[i].v,q[i-1].v,ans);//保證繼承答案連續
			int lca=LCA(q[i].u,q[i].v);
			reverse(lca,ans);
			q[i].ans=ans;
			reverse(lca,ans);//單獨計算,最後要消除影響。不要被繼承。
		}

事實證明,這種寫法是真毒瘤,開了O2才勉強能過。

全程式碼

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<cmath>
using namespace std;
inline int read(){
	int w=0,x=0;char c=getchar();
	while(!isdigit(c))w|=c=='-',c=getchar();
	while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return w?-x:x;
}
namespace star
{
	const int maxn=1e5+10;
	typedef long long ll;
	int n,m,Q,_n,fa[maxn][23],_nsiz,dfn[maxn],dep[maxn],c[maxn],color[maxn];
	ll v[maxn],w[maxn];
	int ecnt,tim,st[maxn<<1],belong[maxn],top,head[maxn],to[maxn<<1],nxt[maxn<<1];
	struct query{
		int u,v,id;
		ll ans;
		inline bool operator < (const query&zp)const {return belong[u]<belong[zp.u] or (belong[u]==belong[zp.u] and (belong[v]<belong[zp.v] or (belong[v]==belong[zp.v] and id<zp.id)));}
	}q[maxn];
	inline bool cmp(query a,query b){return a.id<b.id;}
	struct modify{
		int pos,bef,aft,id;
	}mo[maxn<<1];
	inline void addedge(int a,int b){
		to[++ecnt]=b,nxt[ecnt]=head[a],head[a]=ecnt;
		to[++ecnt]=a,nxt[ecnt]=head[b],head[b]=ecnt;
	}
	int dfs(int x,int f){
		int siz=0;
		fa[x][0]=f;dep[x]=dep[f]+1;
		dfn[x]=++tim;
		for(int i=0;i<=20;i++)fa[x][i+1]=fa[fa[x][i]][i];
		for(int i=head[x];i;i=nxt[i]){
			int u=to[i];
			if(u==f)continue;
			siz+=dfs(u,x);
			if(siz>=_nsiz){
				_n++;
				while(siz)belong[st[top--]]=_n,siz--;
			}
		}
		st[++top]=x;
		return siz+1;
	}
	inline int LCA(int x,int y){
		if(dep[x]<dep[y])swap(x,y);
		for(int i=20;i+1;i--)if(dep[fa[x][i]]>=dep[y])x=fa[x][i];
		if(x==y)return x;
		for(int i=20;i+1;i--)if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
		return fa[x][0];
	}
	bool vis[maxn];
	int num[maxn];
	inline void reverse(const int& x,ll &ans){
		if(vis[x])ans-=1ll*w[num[color[x]]]*v[color[x]],num[color[x]]--;
		else num[color[x]]++,ans+=1ll*w[num[color[x]]]*v[color[x]];
		vis[x]^=1;
	}
	inline void solve(int x,int y,ll &ans){
		while(x!=y){
			if(dep[x]>dep[y])reverse(x,ans),x=fa[x][0];
			else reverse(y,ans),y=fa[y][0];
		}
	}
	inline void work(){
		n=read(),m=read(),Q=read();
		_nsiz=pow(n,0.666666666);
		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++)addedge(read(),read());
		for(int i=1;i<=n;i++)c[i]=color[i]=read();
		int cntq=0,cntm=0,x;
		for(int i=1;i<=Q;i++){
			if(read())q[++cntq].id=i,q[cntq].u=read(),q[cntq].v=read();
			else mo[++cntm].id=i,mo[cntm].bef=c[(x=read())],mo[cntm].pos=x,mo[cntm].aft=c[x]=read();
		}
		dfs(1,0);
		for(int i=1;i<=cntq;i++)if(dfn[q[i].v]<dfn[q[i].u])swap(q[i].u,q[i].v);//對於這種寫法這句其實可有也可無了。這種寫法的一個好處就是不交換不影響正確性。
		++_n;
		while(top)belong[st[top--]]=_n;
		sort(q+1,q+1+cntq);
		int now=0;
		long long ans=0;
		for(int i=1;i<=cntq;i++){
			while(now<cntm and mo[now+1].id<=q[i].id){
				now++;
				if(vis[mo[now].pos])ans-=w[num[color[mo[now].pos]]]*v[color[mo[now].pos]],num[color[mo[now].pos]]--;
				color[mo[now].pos]=mo[now].aft;
				if(vis[mo[now].pos])num[color[mo[now].pos]]++,ans+=1LL*w[num[color[mo[now].pos]]]*v[color[mo[now].pos]];
			}
			while(now>=1 and mo[now].id>=q[i].id){
				if(vis[mo[now].pos])ans-=w[num[color[mo[now].pos]]]*v[color[mo[now].pos]],num[color[mo[now].pos]]--;
				color[mo[now].pos]=mo[now].bef;
				if(vis[mo[now].pos])num[color[mo[now].pos]]++,ans+=1LL*w[num[color[mo[now].pos]]]*v[color[mo[now].pos]];
				now--;
			}
			if(i==1)solve(q[i].u,q[i].v,ans);
			else solve(q[i].u,q[i-1].u,ans),solve(q[i].v,q[i-1].v,ans);
			int lca=LCA(q[i].u,q[i].v);
			reverse(lca,ans);
			q[i].ans=ans;
			reverse(lca,ans);
		}
		sort(q+1,q+1+cntq,cmp);
		for(int i=1;i<=cntq;i++)printf("%lld\n",q[i].ans);
	}
}
signed main(){
	star::work();
	return 0;
}

總結

寫了個假的(gxy稱之為非主流)樹上莫隊。再寫一道免得腦子了裝著奇怪的東西。

因為寫法毒瘤所以#define int long long會T飛。

大家千萬不要學非主流