「WC2013」 糖果公园 - 树上带修莫队

题目描述

Candyland 有一座糖果公园,公园里不仅有美丽的风景、好玩的游乐项目,还有许多免费糖果的发放点,这引来了许多贪吃的小朋友来糖果公园游玩。

糖果公园的结构十分奇特,它由 n n n 个游览点构成,每个游览点都有一个糖果发放处,我们可以依次将游览点编号为 1 1 1 n n n。有 n − 1 n-1 n1 条双向道路连接着这些游览点,并且整个糖果公园都是连通的,即从任何一个游览点出发都可以通过这些道路到达公园里的所有其它游览点。

糖果公园所发放的糖果种类非常丰富,总共有 m m m 种,它们的编号依次为 1 1 1 m m m。每一个糖果发放处都只发放某种特定的糖果,我们用 C i C_i Ci来表示 i i i 号游览点的糖果。

来到公园里游玩的游客都不喜欢走回头路,他们总是从某个特定的游览点出发前往另一个特定的游览点,并游览途中的景点,这条路线一定是唯一的。他们经过每个游览点,都可以品尝到一颗对应种类的糖果。

大家对不同类型糖果的喜爱程度都不尽相同。 根据游客们的反馈打分,我们得到了糖果的美味指数, 第 i i i 种糖果的美味指数为 V i V_i Vi。另外,如果一位游客反复地品尝同一种类的糖果,他肯定会觉得有一些腻。根据量化统计,我们得到了游客第 i i i 次品尝某类糖果的新奇指数 W i W_i Wi。如果一位游客第 i i i 次品尝第 j j j 种糖果,那么他的愉悦指数 H H H 将会增加对应的美味指数与新奇指数的乘积,即 V j × W i V_j\times W_i Vj×Wi。这位游客游览公园的愉悦指数最终将是这些乘积的和。

当然,公园中每个糖果发放点所发放的糖果种类不一定是一成不变的。有时,一些糖果点所发放的糖果种类可能会更改(也只会是 m m m 种中的一种),这样的目的是能够让游客们总是感受到惊喜。

糖果公园的工作人员小 A A A 接到了一个任务,那就是根据公园最近的数据统计出每位游客游玩公园的愉悦指数。但数学不好的小 A A A 一看到密密麻麻的数字就觉得头晕,作为小 A A A 最好的朋友,你决定帮他一把。

数据范围

对于所有的数据: 1 ≤ V i , W i ≤ 1 0 6 , 1 ≤ A i , B i ≤ n , 1 ≤ C i ≤ m , W 1 , W 2 , ⋯   , W n 1\le V_i,W_i\le 10^6,1\le A_i,B_i\le n,1\le C_i\le m,W_1,W_2,\cdots,W_n 1Vi,Wi106,1Ai,Bin,1Cim,W1,W2,,Wn是非递增序列,即对于任意 1 < i ≤ n 1< i\le n 1<in都满足 W i ≤ W i − 1 W_i\le W_{i-1} WiWi1

其他限制条件:
「WC2013」 糖果公园 - 树上带修莫队_第1张图片

分析

对于这道题,读题都是一个巨大的难关。题意简单来说就是给你一棵树,每个点有个颜色,每次询问你一条路径求

∑ c v a l c × ∑ i = 1 c n t c w o r t h i \sum_{c}val_c\times\sum_{i=1}^{cnt_c}worth_i cvalc×i=1cntcworthi

其中 v a l val val表示该颜色的价值, c n t cnt cnt表示其出现的次数, w o r t h i worth_i worthi表示第 i i i次出现的价值,带修改。(语文不好,故题意摘自某大佬题解)

可以很容易地想到,这可以离线然后跑带修莫队。实际上也的确如此。在将树上的带修莫队之间,推荐先去做「SCOI2005」王室联邦,是简单的树分块。然后就利用其中的分块方法,将子树中大于 b l k blk blk的分为一块。将询问离线,修改同样离线,并记录在不同的数组中,记录询问时,同时记录到该询问的修改编号。对询问排序,按照 x x x端块的顺序, y y y端时间戳,和修改时间三个关键字从小到大排序,用一个指针指向最后一个执行的修改。每个询问开始处理前,先用指针通过上下变化同时更新答案,将其指向当前询问所需要的修改编号,然后再从前一个的位置更新到当前位置,再将没有处理的Lca加上,记录答案,再撤销对Lca的修改,因为Lca很烦,不想多管他,所以只让它出个场即可。最终输出答案。

还要注意一下,块的大小不能是 n \sqrt{n} n ,因为有修改操作,为了使时间复杂度平摊,所以块的大小要取 n 2 / 3 n^{2/3} n2/3。还有要开 l o n g   l o n g long\ long long long。总时间复杂度为 O ( n 5 / 3 ) O(n^{5/3}) O(n5/3)。建议 O 2 O2 O2

代码

#include 
#include 
#include 
#include 
using namespace std;
const int N=100005;
struct Edge {int to,nxt;}e[N<<1];//边 
struct Query {int x,y,id,hp;}q[N];//询问 
struct Change {int x,y,last;}c[N];//修改 
int h[N],cnt,hd,hq,dep[N];
int n,m,nq,num[N],f[N][25];
int V[N],W[N],blk,dfn[N];
int bel[N],tot,st[N],top;
int vis[N],mp[N];long long temp,ans[N];
//vis[i]:是否访问节点i;mp[i]:第i种糖果的数量;
//V[i]:第i种糖果的美味指数;W[i]:第i次品尝的新奇指数
//num[x]:节点x所放置的糖果类型;bel[x]:节点x所属块的编号
//dfn[x]:节点x的时间戳;f[][]为倍增预处理数组
//st[],top为栈分块时用 
int Getint() {//快读 
	int s=0,f=1;
	char ch=getchar();
	while (ch<'0'||ch>'9') f=(ch=='-'?-1:1),ch=getchar();
	while (ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
	return s*f;
}
void Putint(long long s) {//快输出 
	if (s<0) {
		putchar('-');
		Putint(-s);
		return;
	}
	if (s>9) Putint(s/10);
	putchar(s%10+'0');
}
void Add_Edge(int x,int y) {//加边 
	e[++cnt]=(Edge){y,h[x]};
	h[x]=cnt;
}
bool cmp(Query a,Query b) {//排序比较方式 
	if (bel[a.x]!=bel[b.x])
		return bel[a.x]<bel[b.x];
	if (dfn[a.y]!=dfn[b.y])
		return dfn[a.y]<dfn[b.y];
	return a.hp<b.hp;
}
void Read() {//读入 
	n=Getint(),m=Getint(),nq=Getint();
	for (int i=1;i<=m;i++)
		V[i]=Getint();
	for (int i=1;i<=n;i++)
		W[i]=Getint();
	for (int i=1;i<n;i++) {
		int u,v;
		u=Getint();v=Getint();
		Add_Edge(u,v);
		Add_Edge(v,u); 
	}
	for (int i=1;i<=n;i++)
		num[i]=Getint();
	for (int i=1;i<=nq;i++) {
		int type,x,y;
		type=Getint();x=Getint();y=Getint();
		if (!type) {
			c[++hd]=(Change){x,y,num[x]};
			num[x]=y;
		} else q[++hq]=(Query){x,y,hq,hd};
	}
}
void Dfs(int x,int fa) {//分块用Dfs求出dfn和dep以及bel 
	int bot=top;
	dfn[x]=++dfn[0];
	dep[x]=dep[fa]+1;
	for (int i=h[x];i;i=e[i].nxt) {
		int y=e[i].to;
		if (y==fa) continue;
		f[y][0]=x;
		Dfs(y,x);
		if (top-bot>=blk) {
			tot++;
			while (top!=bot) bel[st[top--]]=tot;
		}
	}
	st[++top]=x;
}
void Divide() {//分块过程 
	blk=pow(n,2.0/3);
	Dfs(1,1);
	tot++;
	while (top) bel[st[top--]]=tot;
	for (int j=1;(1<<j)<=n;j++)
		for (int i=1;i<=n;i++)
			f[i][j]=f[f[i][j-1]][j-1];
}
int Lca(int x,int y) {//Lca 
	if (dep[x]<dep[y]) swap(x,y);
	for (int i=17;~i;i--)
		if (dep[f[x][i]]>=dep[y]) x=f[x][i];
	if (x==y) return x;
	for (int i=17;~i;i--)
		if (f[x][i]!=f[y][i])
			x=f[x][i],y=f[y][i];
	return f[x][0];
}
void Rev(int x) {//反转,原来有的变为无,原来无的变为有,同时更新答案 
	if (!vis[x]) {
		vis[x]=1;
		mp[num[x]]++;
		temp+=(long long)W[mp[num[x]]]*V[num[x]];
	} else {
		vis[x]=0;
		temp-=(long long)W[mp[num[x]]]*V[num[x]];
		mp[num[x]]--;
	}
}
void Chan(int x,int y) {//更新令num[x]=y
	if (vis[x]) {
		temp-=(long long)W[mp[num[x]]]*V[num[x]];
		mp[num[x]]--;
		num[x]=y;
		mp[num[x]]++;
		temp+=(long long)W[mp[num[x]]]*V[num[x]];
	} else num[x]=y;
}
int Clime(int x,int y) {//爬,x与y爬到Lca(x,y) 
	while (x!=y) {
		if (dep[x]>dep[y]) {
			Rev(x);
			x=f[x][0];
		} else {
			Rev(y);
			y=f[y][0];
		}
	}
	if (dep[x]<dep[y]) return x;
	else return y;
}
void Solve() {
	sort(q+1,q+hq+1,cmp);//排序 
	for (int i=1;i<=hq;i++) {
		int lca;
		while (hd<q[i].hp) {//处理修改,多去少补 
			hd++;
			Chan(c[hd].x,c[hd].y);
		}
		while (hd>q[i].hp) {
			Chan(c[hd].x,c[hd].last);
			hd--;
		}
		if (i==1) lca=Clime(q[i].x,q[i].y);//对第一个要特殊处理 
		else {
			Clime(q[i-1].x,q[i].x);//从前一个转到当前 
			Clime(q[i-1].y,q[i].y);
			lca=Lca(q[i].x,q[i].y);
		}
		Rev(lca);//加入Lca 
		ans[q[i].id]=temp;//记录答案 
		Rev(lca);//删去Lca 
	}
}
void Print() {
	for (int i=1;i<=hq;i++)
		Putint(ans[i]),putchar('\n');
}
int main() {
	Read();//读入 
	Divide();//分块 
	Solve();//解决 
	Print();//输出 
	return 0;
}

你可能感兴趣的:(数据结构---莫队,莫队,带修莫队,树上莫队,树上带修莫队,树分块)