【模板】树链剖分
来源:互联网 发布:装配式钢结构 知乎 编辑:程序博客网 时间:2024/05/16 07:20
题目描述
如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:
操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z
操作2: 格式: 2 x y 表示求树从x到y结点最短路径上所有节点的值之和
操作3: 格式: 3 x z 表示将以x为根节点的子树内所有节点值都加上z
操作4: 格式: 4 x 表示求以x为根节点的子树内所有节点值之和
输入输出格式
输入格式:
第一行包含4个正整数N、M、R、P,分别表示树的结点个数、操作个数、根节点序号和取模数(即所有的输出结果均对此取模)。
接下来一行包含N个非负整数,分别依次表示各个节点上初始的数值。
接下来N-1行每行包含两个整数x、y,表示点x和点y之间连有一条边(保证无环且连通)
接下来M行每行包含若干个正整数,每行表示一个操作,格式如下:
操作1: 1 x y z
操作2: 2 x y
操作3: 3 x z
操作4: 4 x
输出格式:
输出包含若干行,分别依次表示每个操作2或操作4所得的结果(对P取模)
输入输出样例
输入样例#1:
5 5 2 24
7 3 7 8 0
1 2
1 5
3 1
4 1
3 4 2
3 2 2
4 5
1 5 1 3
2 1 3
输出样例#1:
2
21
说明
对于100%的数据: N≤10^5,M≤10^5
思路
裸的树链剖分(喂这不本来就是模板题吗)
树链剖分其实就是对于一棵树
先进行两遍DFS
第一遍找到他位于树中的深度,他的父节点,包括他在内下方节点的数量
找到数量最多的设为重儿子,有多个相同的任意选一个
将重儿子连成一条重链,其他儿子以自己为初始节点建一条重链
重新编号,编号顺序是第一条重链,其他儿子的重链
以新编号建立线段树,进行对区间的更新和求和(也就是1和2是线段树的基本操作)
3和4对于在同一个重链的节点很好找
对于不在同一重链的先找到更深的,找到它所在重链的链头
再找链头的父亲,再找深度,重复操作
最终是可以到同一条重链的
(话说代码是真的长,注释是真的多)
#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>using namespace std;const long long MAXN=500010;//线段树至少4倍 long long N,M,R,P;//分别表示树的结点个数、操作个数、根节点序号和取模数long long a[MAXN];//存点的权值 long long h[MAXN],cnt; //存图 long long fa[MAXN],dep[MAXN],size[MAXN];//父节点,深 度,包括本身的下方的点的数量 long long hson[MAXN],top[MAXN];//重儿子,重链顶点long long id[MAXN],real[MAXN],num;//新编号和原编号 序号 struct Tu{ long long to,next;}e[MAXN];//图 struct Tr{ long long l,r,flag,sum; }tree[MAXN];//树 inline long long read(){ long long sum=0; char ch=getchar(); while(ch>'9'||ch<'0') ch=getchar(); while(ch<='9'&&ch>='0') sum=sum*10+ch-48,ch=getchar(); return sum;}//读入优化 void add(long long x,long long y){ e[++cnt].to=y; e[cnt].next=h[x]; h[x]=cnt;}//建图 void DFS1(long long u,long long f) //u为该节点,f为父节点 { size[u]=1;//目前链长为一 fa[u]=f,dep[u]=dep[f]+1;//更新父节点和深度 for(int i=h[u];i;i=e[i].next) { long long v=e[i].to; if(v!=f)//如果不是父节点 { DFS1(v,u);//继续深搜 size[u]+=size[v];//递归回来更新链长 if(hson[u]==0||size[hson[u]]<size[v]) hson[u]=v; //更新重儿子,没有重儿子就随意选 } } }//建树void DFS2(long long u,long long first) { top[u]=first;//找到开端 id[u]=++num,real[num]=u;//id为重编号,real存原编号 if(hson[u]==0) return;//叶子即边界 DFS2(hson[u],first);//有重儿子继续找 for(int i=h[u];i;i=e[i].next) { long long v=e[i].to; if(v!=fa[u]&&v!=hson[u]) DFS2(v,v); //其他不是重儿子的子节点单独开一条新链 } }//找重链的顶点,重编号void PushUp(long long root){ tree[root].sum=tree[root<<1].sum+tree[root<<1|1].sum;}//上推操作void PushDown(long long root){ if(tree[root].flag>0) { tree[root<<1].flag+=tree[root].flag; tree[root<<1|1].flag+=tree[root].flag; tree[root<<1].sum+=tree[root].flag*(tree[root<<1].r-tree[root<<1].l+1); tree[root<<1|1].sum+=tree[root].flag*(tree[root<<1|1].r-tree[root<<1|1].l+1); tree[root].flag=0; }}//懒操作 void Build(long long root,long long l,long long r){ tree[root].l=l;tree[root].r=r; if(l==r) { tree[root].sum=a[real[l]]; return; }//设置边界条件 long long mid=(l+r)>>1; Build(root<<1,l,mid); Build(root<<1|1,mid+1,r); PushUp(root);//上推 }//建线段树void Update(long long root,long long ll,long long rr,long long w){ if(ll<=tree[root].l&&rr>=tree[root].r) { tree[root].flag+=w; tree[root].sum+=w*(tree[root].r-tree[root].l+1); return; } PushDown(root); long long mid=(tree[root].l+tree[root].r)>>1; if(rr<=mid) Update(root<<1,ll,rr,w); else if(ll>mid) Update(root<<1|1,ll,rr,w); else { Update(root<<1,ll,mid,w); Update(root<<1|1,mid+1,rr,w); } PushUp(root);}//区间修改 long long Query_sum(long long root,long long ll,long long rr){ if(ll<=tree[root].l&&rr>=tree[root].r) return tree[root].sum; PushDown(root); long long mid=(tree[root].l+tree[root].r)>>1; if(rr<=mid)return Query_sum(root<<1,ll,rr); if(ll>mid) return Query_sum(root<<1|1,ll,rr); return Query_sum(root<<1,ll,mid)+Query_sum(root<<1|1,mid+1,rr);}//区间求和 void change(long long u,long long v,long long x){ long long tu=top[u],tv=top[v];//找到链头 while(tu!=tv) { if(dep[tu]<dep[tv]) swap(tu,tv),swap(u,v);//保持u比v深 Update(1,id[tu],id[u],x); u=fa[tu],tu=top[u]; } if(dep[u]>dep[v]) swap(u,v); Update(1,id[u],id[v],x);//更新从u比v到值所以u要比v浅 }//区间修改 long long find_sum(long long u,long long v){ long long tu=top[u],tv=top[v],sum=0; while(tu!=tv) { if(dep[tu]<dep[tv]) swap(tu,tv),swap(u,v); sum+=Query_sum(1,id[tu],id[u]);sum=sum%P; u=fa[tu],tu=top[u]; } if(dep[u]>dep[v]) swap(u,v); sum+=Query_sum(1,id[u],id[v]); return sum%P;}//区间求和,大致同上 void root_add(long long u,long long x){ long long begin=id[u]; long long end=id[u]+size[u]-1; Update(1,begin,end,x);}//子树修改 long long root_sum(long long u){ long long begin=id[u]; long long end=id[u]+size[u]-1; return Query_sum(1,begin,end)%P;}//子树求和 int main(){ //freopen("1.in","r",stdin); long long z,x,y,ask; N=read(),M=read(),R=read(),P=read(); for(int i=1;i<=N;++i) a[i]=read(); //输入各个点的值 for(int i=1;i<N;++i) { x=read(),y=read(); add(x,y),add(y,x); }//建图 DFS1(R,0);//建树 DFS2(R,R);//找重链的顶点,重编号 Build(1,1,num);//以新编号建线段树 for(int i=1;i<=M;++i) { ask=read(); if(ask==1) x=read(),y=read(),z=read(),change(x,y,z); //区间修改 else if(ask==2) x=read(),y=read(),printf("%d\n",find_sum(x,y)); //区间求和 else if(ask==3) x=read(),z=read(),root_add(x,z); //子树修改 else if(ask==4) x=read(),printf("%d\n",root_sum(x)); //子树求和 } return 0;}
- 树链剖分 模板
- 模板-树链剖分
- 树链剖分模板
- 树链剖分模板
- 树链剖分模板
- 树链剖分模板
- 树链剖分模板
- 树链剖分模板
- 树链剖分模板
- 树链剖分模板
- 【模板】树链剖分
- 树链剖分模板
- 树链剖分模板
- 树链剖分模板
- 树链剖分模板
- 树链剖分模板
- 树链剖分 模板
- 【模板】树链剖分
- 第8章习题
- 编写一个程序,它从标准输入读取C源代码,并验证所有的花括号都正确的成对出现。
- Head First Java学习心得
- iOS开发问题---xcode9 iphone7真机模拟出现 iPhone7 is busy:Preparing debugger support for ....
- Ubuntu内核编译教程-编译Linux4.5.0内核
- 【模板】树链剖分
- 正交匹配追踪
- Qt笔记_20
- Oracle Database Daily Check
- <划分DP>【noip 2001】codevs 1039 数的划分
- 【BZOJ1083】【MST】[SCOI2005]繁忙的都市 题解
- halcon绘制多边形轮廓的方法
- bzoj 3810: [Coci2015]Stanovi 动态规划
- cordova打包Android时Gradle报错