疯狂动物城

题目背景

原题空间限制 512MB。


Nick 是只在动物城以坑蒙拐骗为生的狐狸,儿时受到偏见的伤害,放弃了自己的理想。他被兔子 Judy 设下圈套,被迫与她合作查案,而卷入意想不到的阴谋,历尽艰险后成为搭档。他们识破了绵羊副市长 Bellwether 的计划,发现是 Bellwether 陷害食肉动物,用毒药让食肉动物发狂。Bellwether 被抓到了监狱里面, Nick 和 Judy 过上了一段平静的日子。

然而,故事并没有这样结束,之前在车管所帮他们查车牌号的憨厚的树懒 Flash,才是陷害食肉动物事件的幕后主使。Flash 批量制作了大量让食肉动物发狂的药剂,投放到了食肉动物群中。现在,大量的食肉动物被感染,动物城陷入了一片混乱。警察局的牛局长 Bogo 找到了 Nick,希望他能帮忙。幸运的是,动物城联邦安全局非常有先见之明,他们在每个州都秘密放置了一台机器,机器能生产能量石,这些能量石能让食肉动物恢复正常。现在 Nick 和 Judy 需要去启动这些机器。

题目描述

提示:我们在文末提供了一份形式化题意。

动物城是一个有 N N N 个州的联邦,该联邦是一个树的形状,即 N N N 个州共有 N − 1 N-1 N1 条双向道路连接它们,且 N N N 个州是相互连通的。 N N N 个州的编号依次为 1 , 2 , 3 , … , N 1,2,3,\dots,N 1,2,3,,N。每个州都有且仅有一台机器。一台机器启动后的下一个时刻,就会开始生产能量石,每个单位时间生产一个。能量石从被生产的时刻开始即生效,每一个单位时间能救一定数量的食肉动物。每个州的解毒机器制造出的能量石的品种可能是不同,第 i i i 个州的机器生产的能量石每个单位时间能救 a i a_i ai 只食肉动物。

Nick 和 Judy 剩下的时间不多了,他们决定分工合作。 Nick 从 X X X 州出发,目的地为 Y Y Y 州,路径为 X X X Y Y Y 的最短路径。 Nick 从 X X X 州出发的时刻为 0 0 0,每隔一个单位时间移动一个州。每到一个州,Nick 就会启动这个州的机器。 Nick 想知道他从 X X X 州出发到达 Y Y Y 州的这段时间里,一共有多少食肉动物被拯救。Nick 在纠结他的路线选择,因此,他会给你若干的询问,希望比他更聪明的你能帮助他。

在他给你询问的过程中,动物城的局势也在发生着一些变化。动物城联邦安全局可以执行一个修改操作 X , Y , Δ X,Y,\Delta X,Y,Δ,会对 X X X 州到 Y Y Y 州的最短路径上的州(包括 X , Y X,Y X,Y 州)的机器进行升级,这样,这些机器生产出来的能量石,每个单位时间能救的食肉动物的数量会增加 Δ \Delta Δ

树懒 Flash 当然也不会坐以待毙,他有一台监控仪,会监控每个州的机器的情况,每当有机器被升级,监控仪就会保存下当前所有州的机器的属性 a i a_i ai。Flash 可以用一种神秘的武器执行一个读取操作 X X X,把当前各个州的机器恢复到第 X X X 次保存的状态( X = 0 X=0 X=0 表示未进行过升级时的初始状态)。注意,只有修改操作执行的时后会进行保存。

现在,依次给出 M M M 个操作,若该操作为一个询问,请你输出 Nick 在当前局面下,他从 X X X 州出发到达 Y Y Y 州的这段时间里,一共有多少食肉动物被拯救,由于这个答案可能很大,你只需要输出答案模 20160501 20160501 20160501 后的值。请注意, M M M 个操作都是被加密过的。


形式化题意

给你一棵 N N N 个点的树,接下来有三种操作共 M M M 次:

  • 1 x y w,表示将 x x x y y y 的路径上的所有点的点权加上 w w w
  • 2 x y,表示一次询问。记 x x x y y y 的路径上的点集为 S ( x , y ) S(x,y) S(x,y),点 p , q p,q p,q 之间的路径长度为 dis ( p , q ) \text{dis}(p,q) dis(p,q),求出 ∑ i ∈ S ( x , y ) ∑ j ≤ dis ( i , y ) a i ⋅ j \sum_{i\in S(x,y)}\sum_{j\le \text{dis}(i,y)}a_i\cdot j iS(x,y)jdis(i,y)aij 的值。将答案对 20160501 20160501 20160501 取模。
  • 3 x,表示将这棵树的所有点权恢复到第 x x x1 操作之后的状态。

强制在线。

输入格式

第一行 2 2 2 个整数 N , M N,M N,M 表示节点个数和操作次数。

接下来 N − 1 N-1 N1 每行 2 2 2 个整数 u , v u,v u,v 表示了这棵树中 u u u v v v 2 2 2 个州间有边相连。

接下来一行 N N N 个整数, 表示这 N N N 个州的机器制造的能量 a i a_i ai 的初始值。

接下来 M M M 行每行先有一个数字表示了操作的类型:

  • 类型 1,代表一个修改操作,接下来有 3 3 3 个整数 X ′ , Y ′ , Δ X',Y',\Delta X,Y,Δ
  • 类型 2,代表一个询问操作,接下来有 2 2 2 个整数 X ′ , Y ′ X',Y' X,Y
  • 类型 3,代表一次读取操作,接下来 1 1 1 个整数 X ′ X' X

其中 X ′ , Y ′ X',Y' X,Y 表示加密后的数字。正确的 X = X ′ xor lastans , Y = Y ′ xor lastans X=X' \text{xor lastans} ,Y=Y' \text{xor lastans} X=Xxor lastans,Y=Yxor lastans。其中 lastans \text{lastans} lastans 为上次 2 操作取模后的答案,如果之前没有 2 操作过那么其值等于 0 0 0

输出格式

对于每个操作 2,输出一行,每行一个数,为所询问的答案模 20160501 20160501 20160501 后的值。

输入输出样例 #1

输入 #1

5 6
1 2
2 3
3 4
4 5
1 2 3 4 5
1 1 5 2
3 0
1 1 3 2
1 3 4 2
3 2
2 1 5

输出 #1

73

输入输出样例 #2

输入 #2

5 4
1 2
1 3
2 4
3 5
1 1 1 2 2
1 1 4 2
2 1 4
3 12
2 13 8

输出 #2

12
4

说明/提示

对于所有数据,保证 1 ≤ n , m ≤ 1 0 5 1\le n,m\le 10^5 1n,m105 1 ≤ a i , Δ ≤ 1 0 5 1\le a_i,\Delta\le 10^5 1ai,Δ105 1 ≤ x , y ≤ n 1\le x,y\le n 1x,yn

对于其中 20 % 20\% 20% 的数据,保证 n , m ≤ 2000 n,m\le 2000 n,m2000

对于另外 20 % 20\% 20% 的数据,保证树为一条链,且不含有操作 3

对于另外 40 % 40\% 40% 的数据,保证树为一条链。

题解

问题描述

给定一棵树,要求实现以下操作:

  1. 路径修改‌:将树上 xy 的权值加 k,同时新建一个版本。
  2. 路径查询‌:查询树上 xy 的答案。
  3. 版本回退‌:回到第 x 个版本(初始状态为 0 版本)。

路径答案定义

定义树上 xy 的答案为:
∑ i ∈ R ( x , y ) a i × 2 d i s ( i , y ) ⋅ ( d i s ( i , y ) + 1 ) \sum_{i \in R(x,y)} a_i \times 2^{dis(i,y)} \cdot (dis(i,y) + 1) iR(x,y)ai×2dis(i,y)(dis(i,y)+1)

其中:

  • d i s ( i , y ) dis(i,y) dis(i,y) 表示节点 i 到节点 y 的距离
  • 若指定树的根,则有 d i s ( i , y ) = d e p i + d e p y − 2 d e p l c a dis(i,y) = dep_i + dep_y - 2dep_{lca} dis(i,y)=depi+depy2deplcalcaxy 的最近公共祖先)

公式推导

路径拆分

将路径拆分为两条链:

  • S = [ x , l c a ] S = [x, lca] S=[x,lca]
  • T = ( l c a , y ] T = (lca, y] T=(lca,y]

分别计算两部分贡献:

链 S 的贡献

a n s S = 1 2 ∑ i ∈ S a i ⋅ [ ( d e p i + d e p y − 2 d e p l c a ) 2 + ( d e p i + d e p y − 2 d e p l c a ) ] = 1 2 [ ∑ a i d e p i 2 + ∑ a i d e p i ( 2 d e p y − 4 d e p l c a ) + ∑ a i ( d e p y 2 + 4 d e p l c a 2 − 4 d e p y d e p l c a + d e p y − 2 d e p l c a ) ] \begin{aligned} ans_S &= \frac{1}{2} \sum_{i \in S} a_i \cdot \left[ (dep_i + dep_y - 2dep_{lca})^2 + (dep_i + dep_y - 2dep_{lca}) \right] \\ &= \frac{1}{2} \left[ \sum a_i dep_i^2 + \sum a_i dep_i (2dep_y - 4dep_{lca}) + \sum a_i \left(dep_y^2 + 4dep_{lca}^2 - 4dep_y dep_{lca} + dep_y - 2dep_{lca}\right) \right] \end{aligned} ansS=21iSai[(depi+depy2deplca)2+(depi+depy2deplca)]=21[aidepi2+aidepi(2depy4deplca)+ai(depy2+4deplca24depydeplca+depy2deplca)]

m 1 = d e p y − 2 d e p l c a m_1 = dep_y - 2dep_{lca} m1=depy2deplca,简化为:
a n s S = 1 2 [ ∑ a i d e p i 2 + ∑ a i d e p i ( 2 m 1 + 1 ) + ∑ a i ( m 1 2 + m 1 ) ] ans_S = \frac{1}{2} \left[ \sum a_i dep_i^2 + \sum a_i dep_i (2m_1 + 1) + \sum a_i (m_1^2 + m_1) \right] ansS=21[aidepi2+aidepi(2m1+1)+ai(m12+m1)]

链 T 的贡献

a n s T = 1 2 ∑ i ∈ T a i ⋅ [ ( d e p y − d e p i ) 2 + ( d e p y − d e p i ) ] = 1 2 [ ∑ a i d e p i 2 − ∑ a i d e p i ( 2 d e p y + 1 ) + ∑ a i ( d e p y 2 + d e p y ) ] \begin{aligned} ans_T &= \frac{1}{2} \sum_{i \in T} a_i \cdot \left[ (dep_y - dep_i)^2 + (dep_y - dep_i) \right] \\ &= \frac{1}{2} \left[ \sum a_i dep_i^2 - \sum a_i dep_i (2dep_y + 1) + \sum a_i \left(dep_y^2 + dep_y\right) \right] \end{aligned} ansT=21iTai[(depydepi)2+(depydepi)]=21[aidepi2aidepi(2depy+1)+ai(depy2+depy)]

m 2 = d e p y m_2 = dep_y m2=depy,简化为:
a n s T = 1 2 [ ∑ a i d e p i 2 − ∑ a i d e p i ( 2 m 2 + 1 ) + ∑ a i ( m 2 2 + m 2 ) ] ans_T = \frac{1}{2} \left[ \sum a_i dep_i^2 - \sum a_i dep_i (2m_2 + 1) + \sum a_i (m_2^2 + m_2) \right] ansT=21[aidepi2aidepi(2m2+1)+ai(m22+m2)]


维护方法

需要在线段树上维护以下三个值:

  1. ∑ a i d e p i 2 \sum a_i dep_i^2 aidepi2
  2. ∑ a i d e p i \sum a_i dep_i aidepi
  3. ∑ a i \sum a_i ai

实现要点

  1. DFS 序预处理
    通过 DFS 序维护 d e p i 2 dep_i^2 depi2 d e p i dep_i depi 的前缀和。

  2. 主席树优化

    • 使用 ‌标记永久化‌ 处理区间修改:
      • 修改时若节点区间被完全覆盖,直接添加懒标记并返回
      • 区间未被完全覆盖时递归处理子节点
    • 查询时累加路径上的所有懒标记贡献
  3. 空间优化
    避免冗余节点创建,通过共用节点和延迟更新降低空间复杂度。

#include<bits/stdc++.h>
#define int long long
//这题有很多地方是要强制类型转换的,为了防止出错,这里使用 define int long long
#define L t[x].lc
#define R t[x].rc
#define mid (l+r>>1)
#define rep(x,y,z) for(int x=(y);x<=(z);x++)
#define per(x,y,z) for(int x=(y);x>=(z);x--)
#define e(x) for(int i=h[x];i;i=nxt[i])
inline int read(){int s=0,w=1;char c=getchar();while(c<48||c>57) {if(c=='-') w=-1;c=getchar();}while(c>=48&&c<=57) s=(s<<1)+(s<<3)+c-48,c=getchar();return s*w;}
inline void pf(int x){if(x<0) putchar('-'),x=-x;if(x>9)pf(x/10);putchar(x%10+48);}
const int N =1e5+5,M=4e7+5,inf=2147000000;
const int mod=20160501;
using namespace std;
int n,m,h[N],to[N*2],nxt[N*2],cnt,a[N];
inline void add(int a,int b){
	to[++cnt]=b,nxt[cnt]=h[a],h[a]=cnt;
}
int dep[N],siz[N],son[N],top[N],f[N],Id[N];//Id[i]是代表欧拉序对应的节点的原编号
inline void dfs(int x,int fa){
	/*
    这是常规树链剖分
    */
}
int dfn[N],Time;
inline void Dfs(int x,int low){
	/*
    这是常规树链剖分
   */
}
struct node{
	int lc,rc;
	int w;//点权和
	int laz;//永久化标记
	int smul;//点权乘深度
	int spow;//点权乘深度方
	bool nl,nr;//这两个变量记录左右儿子是否已经新建过了
}t[N*130];
int tot,root[N],sd[N],sdd[N];
inline int getd(int l,int r){
	return (sd[r]-sd[l-1]+mod)%mod;
}
inline int getdd(int l,int r){
	return (sdd[r]-sdd[l-1]+mod)%mod;
}
//这一段是前缀和
inline void insert(int x,int l,int r,int k){
	if(!k)return;
	(t[x].w+=(r-l+1)*k%mod)%=mod,(t[x].smul+=getd(l,r)*k%mod)%=mod,(t[x].spow+=getdd(l,r)*k%mod)%=mod;
    //这一段插入一个节点对应的值,这里的l和r是需要修改的部分,而不是这个节点的l和r
}
inline void getup(int x){
	t[x].w=(t[L].w+t[R].w)%mod,t[x].smul=(t[L].smul+t[R].smul)%mod,t[x].spow=(t[L].spow+t[R].spow)%mod;//只在建树的时候使用
}
inline int build(int &x,int l,int r){
	x++;
	if(l==r){
    /*
    这是建树的部分,这里填什么请自己完善
    */
    return x;}
	int now=x;
	t[now].lc=build(x,l,mid),t[now].rc=build(x,mid+1,r);
	getup(now);
	return now;
}
inline void modify(int x,int l,int r,int Ll,int Rr,int k){
	insert(x,max(l,Ll),min(r,Rr),k);//该怎么添加就怎么添加
	if(l>=Ll&&r<=Rr){
		t[x].laz+=k;//被覆盖,打上标记
		return;
	}
	if(Rr<=mid){
		if(t[x].nl)t[x].nl=0,t[++tot]=t[L],L=tot,t[L].nl=t[L].nr=1;//待修改的节点在原树上,需要新建节点
		modify(L,l,mid,Ll,Rr,k);
	}
	else if(Ll>mid){
		if(t[x].nr)t[x].nr=0,t[++tot]=t[R],R=tot,t[R].nl=t[R].nr=1;//待修改的节点在原树上,需要新建节点
		modify(R,mid+1,r,Ll,Rr,k);
	}
	else {
		if(t[x].nl)t[x].nl=0,t[++tot]=t[L],L=tot,t[L].nl=t[L].nr=1;
		if(t[x].nr)t[x].nr=0,t[++tot]=t[R],R=tot,t[R].nl=t[R].nr=1;//待修改的节点在原树上,需要新建节点
		modify(L,l,mid,Ll,Rr,k),modify(R,mid+1,r,Ll,Rr,k);
	}
}
inline node queryw(int x,int l,int r,int Ll,int Rr,node now){
	if(l>=Ll&&r<=Rr){
		(now.w+=t[x].w)%=mod,(now.smul+=t[x].smul)%=mod,(now.spow+=t[x].spow)%=mod; 
               //如果覆盖,加上该节点的信息
		return now;
	}
	int nl=max(l,Ll),nr=min(r,Rr);
	(now.w+=(nr-nl+1)*t[x].laz)%=mod,(now.smul+=getd(nl,nr)*t[x].laz)%=mod,(now.spow+=getdd(nl,nr)*t[x].laz)%=mod; //累加路上的标记
	if(Rr<=mid)return queryw(L,l,mid,Ll,Rr,now);
	if(Ll>mid)return queryw(R,mid+1,r,Ll,Rr,now);
	return queryw(L,l,mid,Ll,Rr,queryw(R,mid+1,r,Ll,Rr,now));
}
inline int Lca(int x,int y){
	/*
    这是常规树链剖分求LCA
   */
}
int lastans,cmt,nowroot;//cmt是记录当前是第几次1操作
inline void lca_(int x,int y,int z){
	/*
    这是常规的树链剖分的修改
   */ 
}
inline int lca_q(int x,int y,int lca){
	int ans=0;
    int wkk=(2*dep[y]-4*dep[lca]+1+mod)%mod,wk=(dep[y]*dep[y]%mod+4*dep[lca]%mod*dep[lca]%mod-4*dep[y]%mod*dep[lca]%mod+dep[y]-2*dep[lca]+mod)%mod,yk=dep[y]*(dep[y]+1)%mod,dk=(dep[y]*2+1)%mod;//这里的变量一一对应了题解中的常数
	node now;
	while(top[x]^top[lca]){
		now.w=now.smul=now.spow=0;
		now=queryw(root[nowroot],1,n,dfn[top[x]],dfn[x],now);
		ans+=now.w*wk%mod;
		ans+=now.spow;
		ans+=now.smul*wkk%mod;
		ans%=mod;
		x=f[top[x]];
	}
	if(x^lca){
		now.w=now.smul=now.spow=0;
		now=queryw(root[nowroot],1,n,dfn[lca]+1,dfn[x],now);
		ans+=now.w*wk%mod;
		ans+=now.spow;
		ans+=now.smul*wkk%mod;
		ans%=mod;
	}//代码是把 x 放在了 T 集合,不过放哪个集合都正确
   //这上面求的是 S 集合的部分
   //这下面求的是 T 集合的部分
	while(top[y]^top[lca]){
		now.w=now.smul=now.spow=0;		
		now=queryw(root[nowroot],1,n,dfn[top[y]],dfn[y],now);
		ans+=now.w*yk%mod;
		ans+=now.spow%mod;
		ans-=now.smul*dk%mod;
		ans=(ans%mod+mod)%mod;
		y=f[top[y]];
	}
	now.w=now.smul=now.spow=0;
	now=queryw(root[nowroot],1,n,dfn[lca],dfn[y],now);
	ans+=now.w*yk%mod;
	ans+=now.spow%mod;
	ans-=now.smul*dk%mod;
	ans=(ans%mod+mod)%mod;
	return ans*((mod+1)/2)%mod;
}
inline void prep(){
	rep(i,1,n)sd[i]=(sd[i-1]+dep[Id[i]])%mod,sdd[i]=(sdd[i-1]+dep[Id[i]]*dep[Id[i]]%mod)%mod;
}
signed main(){
	/*
    这一段是正常的读入和预处理
   */
	for(int i=1,opt,x,y,k;i<=m;i++){
		opt=read();
		if(opt==1){
			x=read()^lastans,y=read()^lastans,k=read();
			root[++cmt]=++tot;
			t[root[cmt]]=t[root[nowroot]];
			nowroot=cmt;
			t[root[nowroot]].nl=t[root[nowroot]].nr=1;//修改操作,要新建一棵树
			lca_(x,y,k);
		}
		else if(opt==2){
			x=read()^lastans,y=read()^lastans,k=Lca(x,y);
			lastans=lca_q(x,y,k);
			pf(lastans),putchar('\n');
		}
		else x=read()^lastans,nowroot=x;//回到对应的版本
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值