#139. 树链剖分
139.树链剖分
树上维护 \(5\) 个操作:
- 换根
- 路径加
- 子树加
- 路径和
- 子树和
Solution
你管这叫模板题???
如果去掉操作 \(1\) 换根,那此题就是一道平平无奇的纯树剖模板题,但是换根这一操作的加入,瞬间加大了这道题的难度。
观察可以发现,与路径相关的操作是与换根无关的,也就是说路径信息的维护仍然可以沿用一般树剖的办法,但是换根会影响子树的信息。我最开始看到换根的时候想到的是 \(\texttt{LCT}\),甚至都研究出来怎么查询子树和了,结果发现 \(\texttt{LCT}\) 根本没法维护子树加这一操作,所以还是只能恋恋不舍的舍弃掉这个想法。
还是尝试用普通树剖来解决。如果每次换根都暴力重构整棵树的重轻链信息的话,显然每次的时间复杂度都是 \(\mathcal O(n\log n)\)(因为还需要重构线段树),完全承受不住。换言之,我们不能重构这棵树。考虑怎么在不重构整棵树的情况下用原来的根的信息来维护换根后的信息。分三种情况讨论(旧根下的当前节点为 \(rt\),新根为 \(nr\),下面的子树都是在旧根下的情况)。
-
\(rt=nr\)
此时很方便,直接对整棵树进行操作即可,方法与一般树剖一致。 -
\(nr\) 不在 \(rt\) 的子树中
画下图,这也就意味着 \(nr\) 为根的时候与旧根的情况是一致的,不需要特殊处理。直接按照一般树剖的办法处理即可。 -
\(nr\) 在 \(rt\) 的子树中
这是最麻烦的情况。不过可以发现,需要修改的部分就是整棵树除去 \(rt\) 向 \(nr\) 方向的子树。因此可以通过修改两次的方法,第一次对整棵树进行修改,第二次对 \(rt\) 向 \(nr\) 方向的子树进行反向修改(比如操作是 \(+v\),那么第一次就是整棵树 \(+v\),第二次就是子树 \(-v\))。
上述情况中,\(1,2\) 情况都不需要特殊处理,第 \(3\) 种情况需要处理出 \(rt\) 向 \(nr\) 方向的最浅的节点,在线段树上多维护这一个信息即可。而三种情况的判断可以用 \(\texttt{LCA}\) 判断,此题因为已经做了树剖,因此就可以直接用树剖求 \(\texttt{LCA}\) 而不需要再写倍增求 \(\texttt{LCA}\) 了。
Code
代码足足有 \(4.6K\),不过好在细节不多,调试起来还算比较容易。不过 \(4.6K\) 的码量在我写过的题中也能排到数一数二的位置了(我记得上一个这么长的代码还是洛谷上的第二代图灵机)。
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof a)
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[1<<23],*p1=buf,*p2=buf;
#define int long long
using namespace std;
template<typename T> void read(T &k)
{
k=0;T flag=1;char b=getchar();
while (!isdigit(b)) {flag=(b=='-')?-1:1;b=getchar();}
while (isdigit(b)) {k=k*10+b-48;b=getchar();}
k*=flag;
}
template<typename T> void write(T k) {if (k<0) {putchar('-'),write(-k);return;}if (k>9) write(k/10);putchar(k%10+48);}
template<typename T> void writewith(T k,char c) {write(k);putchar(c);}
const int _SIZE=1e5;
int root=1,n,m;
struct EDGE{
int nxt,to;
}edge[(_SIZE<<1)+5];
int tot,head[_SIZE+5];
void AddEdge(int x,int y) {edge[++tot]=(EDGE){head[x],y};head[x]=tot;}
int top[_SIZE+5],fa[_SIZE+5],son[_SIZE+5],siz[_SIZE+5],step;
int dep[_SIZE+5],val[_SIZE+5],id[_SIZE+5],a[_SIZE+5],p[_SIZE+5];
int sum[(_SIZE<<2)+5],tag[(_SIZE<<2)+5],minp[(_SIZE<<2)+5];
void dfs1(int x,int f)//树剖
{
fa[x]=f,siz[x]=1,dep[x]=dep[f]+1;
int maxson=-1;
for (int i=head[x];i;i=edge[i].nxt)
{
int twd=edge[i].to;
if (twd==f) continue;
dfs1(twd,x);
siz[x]+=siz[twd];
if (siz[twd]>maxson) maxson=siz[twd],son[x]=twd;
}
}
void dfs2(int x,int topf)
{
top[x]=topf,val[++step]=a[x],id[x]=step,p[step]=x;
if (son[x]) dfs2(son[x],topf);
for (int i=head[x];i;i=edge[i].nxt)
{
int twd=edge[i].to;
if (twd==son[x] || twd==fa[x]) continue;
dfs2(twd,twd);
}
}
#define LC (k<<1)
#define RC ((k<<1)|1)
#define mid ((l+r)>>1)
int tmin(int x,int y) //返回x,y中更浅的一个
{
if (x==0 || y==0) return x+y;
return dep[x]<dep[y]?x:y;
}
void maintain(int k) {sum[k]=sum[LC]+sum[RC];minp[k]=tmin(minp[LC],minp[RC]);}//上传
void pushdown(int k,int l,int r)//下传
{
if (!tag[k]) return;
tag[LC]+=tag[k],tag[RC]+=tag[k];
sum[LC]+=(mid-l+1)*tag[k],sum[RC]+=(r-mid)*tag[k];
tag[k]=0;
}
void build(int k,int l,int r)//建树
{
if (l==r) {sum[k]=val[l],minp[k]=p[l];return;}
build(LC,l,mid),build(RC,mid+1,r),maintain(k);
}
void update(int k,int l,int r,int a,int b,int v)//区间加
{
if (l>b || r<a) return;
if (l>=a && r<=b)
{
sum[k]+=(r-l+1)*v;
tag[k]+=v;
return;
}
pushdown(k,l,r),update(LC,l,mid,a,b,v),update(RC,mid+1,r,a,b,v),maintain(k);
}
int query(int k,int l,int r,int a,int b)//查询区间和
{
if (l>b || r<a) return 0;
if (l>=a && r<=b) return sum[k];
pushdown(k,l,r);
return query(LC,l,mid,a,b)+query(RC,mid+1,r,a,b);
}
int find(int k,int l,int r,int a,int b)//查询区间最浅点
{
if (l>b || r<a) return 0;
if (l>=a && r<=b) return minp[k];
return tmin(find(LC,l,mid,a,b),find(RC,mid+1,r,a,b));
}
int LCA(int x,int y)//树剖求LCA
{
while (top[x]!=top[y])
{
if (dep[top[x]]<dep[top[y]]) swap(x,y);
x=fa[top[x]];
}
if (dep[x]>dep[y]) swap(x,y);
return x;
}
int Upto(int x,int y)//查询x->y路径上深度最浅的点
{
int res=0;
while (top[x]!=top[y])
{
if (dep[top[x]]<dep[top[y]]) swap(x,y);
res=tmin(res,find(1,1,n,id[top[x]],id[x]));
x=fa[top[x]];
}
if (dep[x]>dep[y]) swap(x,y);
res=tmin(res,find(1,1,n,id[x]+1,id[y]));
return res;
}
int check(int x)//判断是三种情况中的哪一种,0对应第一种,-1对应第二种,第三种返回x->root上的最浅点
{
if (x==root) return 0;
int lca=LCA(x,root);
if (lca==x) return Upto(x,root);
return -1;
}
void AddChain(int x,int y,int v)//路径加
{
while (top[x]!=top[y])
{
if (dep[top[x]]<dep[top[y]]) swap(x,y);
update(1,1,n,id[top[x]],id[x],v);
x=fa[top[x]];
}
if (dep[x]>dep[y]) swap(x,y);
update(1,1,n,id[x],id[y],v);
}
int AskChain(int x,int y)//路径查
{
int res=0;
while (top[x]!=top[y])
{
if (dep[top[x]]<dep[top[y]]) swap(x,y);
res+=query(1,1,n,id[top[x]],id[x]);
x=fa[top[x]];
}
if (dep[x]>dep[y]) swap(x,y);
return res+query(1,1,n,id[x],id[y]);
}
void AddTree(int x,int v)//子树加
{
int type=check(x);
if (!type) update(1,1,n,1,n,v);//1
else if (type>0) update(1,1,n,1,n,v),update(1,1,n,id[type],id[type]+siz[type]-1,-v);//3
else update(1,1,n,id[x],id[x]+siz[x]-1,v);//2
}
int AskTree(int x)//子树查
{
int type=check(x);
if (!type) return query(1,1,n,1,n);//1
else if (type>0) return query(1,1,n,1,n)-query(1,1,n,id[type],id[type]+siz[type]-1);//3
else return query(1,1,n,id[x],id[x]+siz[x]-1);//2
}
signed main()
{
read(n);
for (int i=1;i<=n;i++) read(a[i]);
for (int i=2,temp;i<=n;i++) read(temp),AddEdge(temp,i),AddEdge(i,temp);
dfs1(1,0),dfs2(1,1),build(1,1,n);
read(m);
for (int i=1;i<=m;i++)
{
int opt,x,y,z;read(opt),read(x);
if (opt==1) root=x;
else if (opt==2) read(y),read(z),AddChain(x,y,z);
else if (opt==3) read(y),AddTree(x,y);
else if (opt==4) read(y),writewith(AskChain(x,y),'\n');
else writewith(AskTree(x),'\n');
}
return 0;
}