树链剖分

已知一棵包含 N 个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:

  • 1 x y z,表示将树从 xy 结点最短路径上所有节点的值都加上 z

  • 2 x y,表示求树从 xy 结点最短路径上所有节点的值之和。

  • 3 x z,表示将以 x 为根节点的子树内所有节点值都加上 z

  • 4 x 表示求以 x 为根节点的子树内所有节点值之和

输入格式

第一行包含 4 个正整数 N,M,R,P,分别表示树的结点个数、操作个数、根节点序号和取模数(即所有的输出结果均对此取模)。

接下来一行包含 N 个非负整数,分别依次表示各个节点上初始的数值。

接下来 N1 行每行包含两个整数 x,y,表示点 x 和点 y 之间连有一条边(保证无环且连通)。

接下来 M 行每行包含若干个正整数,每行表示一个操作。

输出格式

输出包含若干行,分别依次表示每个操作 2 或操作 4 所得的结果(P 取模)。

对于 100% 的数据: 1N1051M1051RN1P2311。所有输入的数均在 int 范围内。

时间复杂度O(m logn logn)

#define LL long long #define lc u<<1 #define rc u<<1|1 const int N=100010; int n,m,a,b,root,P,w[N]; vector<int> e[N]; int fa[N],dep[N],sz[N],son[N]; int top[N],id[N],nw[N],cnt; //重链 struct tree{ int l,r; LL add,sum; }tr[N*4]; //线段树 void dfs1(int u,int father){//搞fa,dep,sz,son fa[u]=father,dep[u]=dep[father]+1,sz[u]=1; for(int v:e[u]){ if(v==father) continue; dfs1(v,u); sz[u]+=sz[v]; if(sz[son[u]]<sz[v]) son[u]=v; } } void dfs2(int u,int t){ //搞top,id,nw top[u]=t,id[u]=++cnt,nw[cnt]=w[u]; if(!son[u]) return; dfs2(son[u],t); for(int v:e[u]){ if(v==fa[u]||v==son[u])continue; dfs2(v,v); } } void pushup(int u){ tr[u].sum=tr[lc].sum+tr[rc].sum; } void pushdown(int u){ if(tr[u].add){ tr[lc].sum+=tr[u].add*(tr[lc].r-tr[lc].l+1); tr[rc].sum+=tr[u].add*(tr[rc].r-tr[rc].l+1); tr[lc].add+=tr[u].add; tr[rc].add+=tr[u].add; tr[u].add=0; } } void build(int u,int l,int r){ //构建线段树 tr[u]={l,r,0,nw[r]}; if(l==r) return; int mid=l+r>>1; build(lc,l,mid),build(rc,mid+1,r); pushup(u); } LL query(int u,int l,int r){ //线段树查询 if(l<=tr[u].l&&tr[u].r<=r)return tr[u].sum; pushdown(u); int mid=tr[u].l+tr[u].r>>1; LL res=0; if(l<=mid) res+=query(lc,l,r); if(r>mid) res+=query(rc,l,r); return res; } LL query_path(int u,int v){ //查询路径 LL res=0; while(top[u]!=top[v]){ if(dep[top[u]]<dep[top[v]]) swap(u,v); res+=query(1,id[top[u]],id[u]); u=fa[top[u]]; } if(dep[u]<dep[v]) swap(u,v); res+=query(1,id[v],id[u]); //最后一段 return res; } LL query_tree(int u){ //查询子树 return query(1,id[u],id[u]+sz[u]-1); } void update(int u,int l,int r,int k){ //线段树修改 if(l<=tr[u].l&&tr[u].r<=r){ tr[u].add+=k; tr[u].sum+=k*(tr[u].r-tr[u].l+1); return; } pushdown(u); int mid=tr[u].l+tr[u].r>>1; if(l<=mid) update(lc,l,r,k); if(r>mid) update(rc,l,r,k); pushup(u); } void update_path(int u,int v,int k){ //修改路径 while(top[u]!=top[v]){ if(dep[top[u]]<dep[top[v]]) swap(u,v); update(1,id[top[u]],id[u],k); u=fa[top[u]]; } if(dep[u]<dep[v]) swap(u,v); update(1,id[v],id[u],k); //最后一段 } void update_tree(int u,int k){ //修改子树 update(1,id[u],id[u]+sz[u]-1,k); } int main(){ scanf("%d%d%d%d",&n,&m,&root,&P); for(int i=1; i<=n; i++) scanf("%d",&w[i]); for(int i=0; i<n-1; i++){ scanf("%d%d",&a,&b); e[a].push_back(b); e[b].push_back(a); } dfs1(root,0); dfs2(root,root); //把树拆成链 build(1,1,n); //用链建线段树 while(m--){ int t,u,v,k; scanf("%d%d",&t,&u); if(t==1){ scanf("%d%d",&v,&k); update_path(u,v,k); } else if(t==3){ scanf("%d",&k); update_tree(u,k); } else if(t==2){ scanf("%d",&v); printf("%d\n",query_path(u,v)%P); } else printf("%d\n",query_tree(u)%P); } }

__EOF__

本文作者爱飞鱼
本文链接https://www.cnblogs.com/mathiter/p/17892130.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   potential-star  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示