[GDOI2016][树链剖分+主席树]疯狂动物城
题面
Description
Nick 是只在动物城以坑蒙拐骗为生的狐狸,儿时受到偏见的伤害,放弃了自己的理想。他被兔子 Judy 设下圈套,被迫与她合作查案,而卷入意想不到的阴谋,历尽艰险后成为搭档。他们识破了绵羊副市长 Bellwether 的计划,发现是 Bellwether 陷害食肉动物,用毒药让食肉动物发狂。Bellwether 被抓到了监狱里面, Nick 和 Judy 过上了一段平静的日子。
然而,故事并没有这样结束,之前在车管所帮他们查车牌号的憨厚的树懒 Flash,才是陷害食肉动物事件的幕后主使。Flash 批量制作了大量让食肉动物发狂的药剂,投放到了食肉动物群中。现在,大量的食肉动物被感染,动物城陷入了一片混乱。警察局的牛局长 Bogo 找到了 Nick,希望他能帮忙。幸运的是,动物城联邦安全局非常有先见之明,他们在每个州都秘密放置了一台机器,机器能生产能量石,这些能量石能让食肉动物恢复正常。现在 Nick 和 Judy 需要去启动这些机器。
动物城是一个有\(N\)个州的联邦,该联邦是一个树的形状,即\(N\)个州共有\(N−1\)条双向道路连接它们,且\(N\)个州是相互连通的。\(N\)个州的编号依次为\(1,2,3,\ldots,N\)。每个州都有且仅有一台机器。一台机器启动后的下一个时刻,就会开始生产能量石, 每个单位时间生产一个。能量石从被生产的时刻开始即生效,每一个单位时间能救一定数量的食肉动物。每个州的解毒机器制造出的能量石的品种可能是不同,第\(i\)个州的机器生产的能量石每个单位时间能救\(a_i\)只食肉动物。
Nick 和 Judy 剩下的时间不多了,他们决定分工合作。 Nick 从\(X\)州出发,目的地为\(Y\) 州,路径为\(X\)到\(Y\)的最短路径。 Nick 从\(X\)州出发的时刻为\(0\),每隔一个单位时间移动一个州。每到一个州, Nick 就会启动这个州的机器。 Nick 想知道他从\(X\)州出发到达\(Y\)州的这段时间里,一共有多少食肉动物被拯救。 Nick 在纠结他的路线选择,因此,他会给你若干的询问,希望比他更聪明的你能帮助他。
在他给你询问的过程中,动物城的局势也在发生着一些变化。动物城联邦安全局可以执行一个修改操作\("X,Y,delta"\),会对\(X\)州到\(Y\)州的最短路径上的州(包括\(X\),\(Y\)州)的机器进行升级,这样,这些机器生产出来的能量石,每个单位时间能救的食肉动物的数量会增加\(delta\)。树懒 Flash 当然也不会坐以待毙,他有一台监控仪,会监控每个州的机器的情况,每当有机器被升级,监控仪就会保存下当前所有州的机器的属性\(a_i\)。 Flash可以用一种神秘的武器执行一个读取操作\("X"\),把当前各个州的机器恢复到第\(X\)次保存的状态($X=0表示未进行过升级时的初始状态)。注意, 只有修改操作执行的时后会进行保存。
现在,依次给出\(M\)个操作,若该操作为一个询问,请你输出 Nick 在当前局面下,他从 \(X\)州出发到达\(Y\)州的这段时间里,一共有多少食肉动物被拯救,由于这个答案可能很大,你只需要输出答案模\(20160501\)后的值。请注意,\(M\)个操作都是被加密过的。
Input
第一行 2 个整数\(N,M\)表示节点个数和操作次数。
接下来\(N−1\)每行\(2\)个整数\(U_i,V_i\)表示了这棵树中\(U_i\)和\(V_i\)这\(2\)个州间有边相连。
接下来一行\(N\)个整数, 表示这\(N\)个州的机器制造的能量的初始值。
接下来\(M\)行每行先有一个数字表示了操作的类型:
类型\(1\),代表一个修改操作,接下来有\(3\)个整数\(X_1,Y_1,delta\),\(X_1,Y_1\)是加密后的数字。正确的\(X=X_1\;xor\;lastans,Y=Y_1\;xor\;lastans\)。\(lastans\)为上次输出的的答案,如果之前没有输出过那么当成\(0\)。
类型 2,代表一个询问操作,接下来有\(2\)个整数\(X_1,Y_1\), 和修改操作一样,正确的 \(X=X_1\;xor\;lastans,Y=Y_1\;xor\;lastans\)。\(lastans\)为上次输出的的答案,如果之前没有输出过那么当成\(0\)。
类型 3,代表一次读取操作,接下来\(1\)个整数\(X1\),正确的\(X=X_1\;xor\;lastans\)。 \(Lastans\)为上次输出的的答案,如果之前没有输出过那么当成\(0\)。
Output
对于每个操作\(2\),输出一行,每行一个数,为所询问的答案模\(20160501\)后的值。
zootopia.in | zootopia.out |
---|---|
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 |
73 |
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 |
12 4 |
HINT
所有的数据\(1≤a_i,delta≤100000\)。保证数据是合法的,不会读取没保存的局面,即\(X≤\) 已经给出的修改操作次数。
\(N≤100000,M≤100000\);
一开始的初值为 1,2,3,4,5。
第 1 个操作为修改操作,修改后变为 3,4,5,6,7。
第 2 个操作为读取操作,读取第 0 次修改后的局面, a1...a5 变为 1,2,3,4,5。
第 3 个操作为修改操作,修改后变为 3,4,5,4,5。
第 4 个操作为修改操作,修改后变为 3,4,7,6,5。
第 5 个操作为读取操作,读取第 2 次修改后的局面, a1...a5 变为 3,4,5,4,5。
第 6 个操作为询问操作,询问从 1 出发走到 5 的时间内有多少食肉动物被拯救。
Nick 在时刻 4 走到州 5。
此时一共被拯救的动物数量为\((3)+(3×2+4)+(3×3+4×2+5)+(3×4+4×3+5×2+4)=73\)
分析
注意:生产的是能量石,而能量石不会消失,一直会有效果,所以每时刻拯救的动物的数量是递增的,而不是每时刻拯救固定数量!也就是说,询问的不是前缀和,而是双重前缀和!
首先,看到这道题我们就会想到树链剖分。那么我们直接树链剖分。
当统计答案时,我们令\(LCA=lca(X,Y)\),分成\([X,LCA]\)和\([LCA,Y]\)两段分别处理。
- 当点\(P\in[X,LCA]\)时,由等差数列求和公式,这个点贡献的答案为:
- 当点\(P\in[LCA,Y]\)时,由等差数列求和公式,这个点贡献的答案为:
于是,我们可以维护权值总和、权值与深度之积的总和、权值与深度的平方之积的总和来统计答案。为了进行更新,我们还要维护深度的总和、深度的平方的总和。当询问时,我们把\(a_i\)的系数、\(a_i dep_i\)的系数传进去即可。
这道题还需要访问历史状态,于是我们用可持久化线段树(主席树)进行维护。为了节省空间,采用标记永久化策略。
代码
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long ll;
const int N=100001;
const ll MOD=20160501;
int n,m,tot,dfx,lst,head[N],v[N<<1],nxt[N<<1],num[N],dep[N],f[N],sum[N],son[N],id[N],dfn[N],pre[N];
void addedge(int x,int y){v[++tot]=y;nxt[tot]=head[x];head[x]=tot;}
void dfs1(int u=1){
sum[u]=1;
for(int i=head[u];~i;i=nxt[i]){
if(v[i]!=f[u]){
f[v[i]]=u;
dep[v[i]]=dep[u]+1;
dfs1(v[i]);
sum[u]+=sum[v[i]];
if(!son[u]||sum[v[i]]>sum[son[u]])son[u]=v[i];
}
}
}
void dfs2(int u=1,int top=1){
id[u]=top;
dfn[u]=++dfx;
pre[dfx]=u;
if(!son[u])return;
dfs2(son[u],top);
for(int i=head[u];~i;i=nxt[i])if(v[i]!=son[u]&&v[i]!=f[u])dfs2(v[i],v[i]);
}
struct PresidentTree{
int nowrt,idx,rtx,rt[N];
struct node{
int ls,rs;
ll sum,dep,dep2,sudep,sudep2,add;
}t[N*50];
void pushup(int o){
t[o].sum=(t[t[o].ls].sum+t[t[o].rs].sum)%MOD;
t[o].dep=(t[t[o].ls].dep+t[t[o].rs].dep)%MOD;
t[o].dep2=(t[t[o].ls].dep2+t[t[o].rs].dep2)%MOD;
t[o].sudep=(t[t[o].ls].sudep+t[t[o].rs].sudep)%MOD;
t[o].sudep2=(t[t[o].ls].sudep2+t[t[o].rs].sudep2)%MOD;
}
void build(int &o,int l,int r){
o=++idx;
if(l==r){
t[o].sum=num[pre[l]];
t[o].dep=dep[pre[l]];
t[o].dep2=dep[pre[l]]*dep[pre[l]]%MOD;
t[o].sudep=t[o].sum*t[o].dep%MOD;
t[o].sudep2=t[o].sum*t[o].dep2%MOD;
return;
}
int mid=(l+r)>>1;
build(t[o].ls,l,mid);
build(t[o].rs,mid+1,r);
pushup(o);
}
pair<ll,ll> update(int rt,int l,int r,int &o,int ql,int qr,ll c){
if(ql>qr)swap(ql,qr);
if(!o){
o=++idx;
t[o]=t[rt];
t[o].ls=t[o].rs=0;
}
if(ql<=l&&r<=qr){
t[o].add=(t[o].add+c)%MOD;
t[o].ls=t[rt].ls;
t[o].rs=t[rt].rs;
return make_pair(t[o].dep,t[o].dep2);
}
int mid=(l+r)>>1;
pair<ll,ll>p,tmp;
if(qr<=mid){
if(!t[o].rs)t[o].rs=t[rt].rs;
if(t[o].ls==t[rt].ls)t[o].ls=0;
p=update(t[rt].ls,l,mid,t[o].ls,ql,qr,c);
}else if(ql>mid){
if(!t[o].ls)t[o].ls=t[rt].ls;
if(t[o].rs==t[rt].rs)t[o].rs=0;
p=update(t[rt].rs,mid+1,r,t[o].rs,ql,qr,c);
}else{
if(t[o].ls==t[rt].ls)t[o].ls=0;
p=update(t[rt].ls,l,mid,t[o].ls,ql,qr,c);
if(t[o].rs==t[rt].rs)t[o].rs=0;
tmp=update(t[rt].rs,mid+1,r,t[o].rs,ql,qr,c);
p.first+=tmp.first;
p.second+=tmp.second;
}
t[o].sum=(t[o].sum+c*(min(qr,r)-max(ql,l)+1))%MOD;
t[o].sudep=(t[o].sudep+c*p.first)%MOD;
t[o].sudep2=(t[o].sudep2+c*p.second)%MOD;
return p;
}
ll query(int o,int l,int r,int ql,int qr,ll tag,ll s1,ll s2){
tag=(tag+t[o].add)%MOD;
if(ql>qr)swap(ql,qr);
if(ql<=l&&r<=qr)return ((t[o].sum+tag*(r-l+1))%MOD*s1+(t[o].sudep+tag*t[o].dep)%MOD*s2+(t[o].sudep2+tag*t[o].dep2)%MOD)%MOD;
ll re=0;
int mid=(l+r)>>1;
if(ql<=mid)re+=query(t[o].ls,l,mid,ql,qr,tag,s1,s2);
if(qr>mid)re+=query(t[o].rs,mid+1,r,ql,qr,tag,s1,s2);
return re%MOD;
}
}pt;
void update(int x,int y,ll del){
++pt.rtx;
while(id[x]!=id[y]){
if(dep[id[x]]<dep[id[y]])swap(x,y);
pt.update(pt.rt[pt.nowrt],1,n,pt.rt[pt.rtx],dfn[id[x]],dfn[x],del);
x=f[id[x]];
}
pt.update(pt.rt[pt.nowrt],1,n,pt.rt[pt.rtx],dfn[x],dfn[y],del);
pt.nowrt=pt.rtx;
}
int LCA(int x,int y){
while(id[x]!=id[y]){
if(dep[id[x]]<dep[id[y]])swap(x,y);
x=f[id[x]];
}
return dep[x]<=dep[y]?x:y;
}
ll query(int x,int y){
ll ans=0;
int b=y,lca=LCA(x,y);
while(id[x]!=id[y]){
if(dep[id[x]]>dep[id[y]]){
ans=(ans+pt.query(pt.rt[pt.nowrt],1,n,dfn[id[x]],dfn[x],0,((ll)dep[b]*(dep[b]+1)-2*dep[lca]*(2*dep[b]-2*dep[lca]+1)+MOD)%MOD,(1+2*dep[b]-4*dep[lca]+MOD)%MOD))%MOD;
x=f[id[x]];
}else{
ans=(ans+pt.query(pt.rt[pt.nowrt],1,n,dfn[id[y]],dfn[y],0,((ll)dep[b]*(dep[b]+1))%MOD,(-1-2*dep[b]+MOD)%MOD))%MOD;
y=f[id[y]];
}
}
if(dep[x]>dep[y])ans=(ans+pt.query(pt.rt[pt.nowrt],1,n,dfn[y],dfn[x],0,((ll)dep[b]*(dep[b]+1)-2*dep[lca]*(2*dep[b]-2*dep[lca]+1)+MOD)%MOD,(1+2*dep[b]-4*dep[lca]+MOD)%MOD))%MOD;
else ans=(ans+pt.query(pt.rt[pt.nowrt],1,n,dfn[x],dfn[y],0,((ll)dep[b]*(dep[b]+1))%MOD,(MOD-1-2*dep[b])%MOD))%MOD;
return ans&1?(ans+MOD)>>1:ans>>1;
}
int main(){
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++){
int x,y;
scanf("%d%d",&x,&y);
addedge(x,y);
addedge(y,x);
}
for(int i=1;i<=n;i++)scanf("%d",&num[i]);
dfs1();
dfs2();
pt.build(pt.rt[0],1,n);
for(int i=1;i<=m;i++){
int opt,x,y;
ll del;
scanf("%d",&opt);
if(opt==1){
scanf("%d%d%lld",&x,&y,&del);
update(x^lst,y^lst,del);
}else if(opt==2){
scanf("%d%d",&x,&y);
printf("%d\n",lst=query(x^lst,y^lst));
}else if(opt==3){
scanf("%d",&x);
pt.nowrt=x^lst;
}
}
}