题目背景
原题空间限制 512MB。
Nick 是只在动物城以坑蒙拐骗为生的狐狸,儿时受到偏见的伤害,放弃了自己的理想。他被兔子 Judy 设下圈套,被迫与她合作查案,而卷入意想不到的阴谋,历尽艰险后成为搭档。他们识破了绵羊副市长 Bellwether 的计划,发现是 Bellwether 陷害食肉动物,用毒药让食肉动物发狂。Bellwether 被抓到了监狱里面, Nick 和 Judy 过上了一段平静的日子。
然而,故事并没有这样结束,之前在车管所帮他们查车牌号的憨厚的树懒 Flash,才是陷害食肉动物事件的幕后主使。Flash 批量制作了大量让食肉动物发狂的药剂,投放到了食肉动物群中。现在,大量的食肉动物被感染,动物城陷入了一片混乱。警察局的牛局长 Bogo 找到了 Nick,希望他能帮忙。幸运的是,动物城联邦安全局非常有先见之明,他们在每个州都秘密放置了一台机器,机器能生产能量石,这些能量石能让食肉动物恢复正常。现在 Nick 和 Judy 需要去启动这些机器。
题目描述
提示:我们在文末提供了一份形式化题意。
动物城是一个有 N N N 个州的联邦,该联邦是一个树的形状,即 N N N 个州共有 N − 1 N-1 N−1 条双向道路连接它们,且 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 ∑i∈S(x,y)∑j≤dis(i,y)ai⋅j 的值。将答案对 20160501 20160501 20160501 取模。3 x
,表示将这棵树的所有点权恢复到第 x x x 次1
操作之后的状态。
强制在线。
输入格式
第一行 2 2 2 个整数 N , M N,M N,M 表示节点个数和操作次数。
接下来 N − 1 N-1 N−1 每行 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=X′xor lastans,Y=Y′xor 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 1≤n,m≤105, 1 ≤ a i , Δ ≤ 1 0 5 1\le a_i,\Delta\le 10^5 1≤ai,Δ≤105, 1 ≤ x , y ≤ n 1\le x,y\le n 1≤x,y≤n。
对于其中 20 % 20\% 20% 的数据,保证 n , m ≤ 2000 n,m\le 2000 n,m≤2000。
对于另外
20
%
20\%
20% 的数据,保证树为一条链,且不含有操作 3
。
对于另外 40 % 40\% 40% 的数据,保证树为一条链。
题解
问题描述
给定一棵树,要求实现以下操作:
- 路径修改:将树上
x
到y
的权值加k
,同时新建一个版本。 - 路径查询:查询树上
x
到y
的答案。 - 版本回退:回到第
x
个版本(初始状态为 0 版本)。
路径答案定义
定义树上 x
到 y
的答案为:
∑
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)
i∈R(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+depy−2deplca(
lca
为x
和y
的最近公共祖先)
公式推导
路径拆分
将路径拆分为两条链:
- 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=21i∈S∑ai⋅[(depi+depy−2deplca)2+(depi+depy−2deplca)]=21[∑aidepi2+∑aidepi(2depy−4deplca)+∑ai(depy2+4deplca2−4depydeplca+depy−2deplca)]
令
m
1
=
d
e
p
y
−
2
d
e
p
l
c
a
m_1 = dep_y - 2dep_{lca}
m1=depy−2deplca,简化为:
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=21i∈T∑ai⋅[(depy−depi)2+(depy−depi)]=21[∑aidepi2−∑aidepi(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[∑aidepi2−∑aidepi(2m2+1)+∑ai(m22+m2)]
维护方法
需要在线段树上维护以下三个值:
- ∑ a i d e p i 2 \sum a_i dep_i^2 ∑aidepi2
- ∑ a i d e p i \sum a_i dep_i ∑aidepi
- ∑ a i \sum a_i ∑ai
实现要点
-
DFS 序预处理
通过 DFS 序维护 d e p i 2 dep_i^2 depi2 和 d e p i dep_i depi 的前缀和。 -
主席树优化
- 使用 标记永久化 处理区间修改:
- 修改时若节点区间被完全覆盖,直接添加懒标记并返回
- 区间未被完全覆盖时递归处理子节点
- 查询时累加路径上的所有懒标记贡献
- 使用 标记永久化 处理区间修改:
-
空间优化
避免冗余节点创建,通过共用节点和延迟更新降低空间复杂度。
#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;
}