#139. 树链剖分
139.树链剖分
树上维护
- 换根
- 路径加
- 子树加
- 路径和
- 子树和
Solution
你管这叫模板题???
如果去掉操作
观察可以发现,与路径相关的操作是与换根无关的,也就是说路径信息的维护仍然可以沿用一般树剖的办法,但是换根会影响子树的信息。我最开始看到换根的时候想到的是
还是尝试用普通树剖来解决。如果每次换根都暴力重构整棵树的重轻链信息的话,显然每次的时间复杂度都是
-
此时很方便,直接对整棵树进行操作即可,方法与一般树剖一致。 -
不在 的子树中
画下图,这也就意味着 为根的时候与旧根的情况是一致的,不需要特殊处理。直接按照一般树剖的办法处理即可。 -
在 的子树中
这是最麻烦的情况。不过可以发现,需要修改的部分就是整棵树除去 向 方向的子树。因此可以通过修改两次的方法,第一次对整棵树进行修改,第二次对 向 方向的子树进行反向修改(比如操作是 ,那么第一次就是整棵树 ,第二次就是子树 )。
上述情况中,
Code
代码足足有
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理