树链剖分
这是个让初学者望而却步的东西,不管打了多少次,也很难一遍过(我太弱了)
根据这个树锯结构可知,这是个锯树结构。它把一棵树拆分成若干条链,从而利用一些其他的数据结构来维护每一条链
常见的路径剖分的方法是轻重树链剖分(启发式剖分)
那我们先来康康毒链剖分有哪些操作吧!
定义
在轻重链剖分中,对于一个节点,他的一颗以儿子为根节点,节点数最多的子树称为重儿子,其连边称为重边,其余称为轻边,重边组成的链叫做重链
所以第一次我们dfs,标记重儿子重边并且记录字树大小和深度
(红线为与重儿子连边的重边,其余为轻边)
另外,轻重链剖分,从任一节点向根节点走 走过的重链和轻边的条数都是log级别以内的
由于任一轻儿子对应的子树大小要小于父节点所对应子树大小的一半 因此从一个轻儿子沿轻边向上跳后 所对应的子树大小至少要乘上2 经过的轻边条数自然是不超过log2N的 由于重链和重链之间只能是轻链,不然会被合并 所以重链条数不超过轻边条数+1 所以重链的条数也是log级别
预处理
1 void dfs1(int x,int f){ 2 size[x]=1;//当前节点 3 fa[x]=f;//记录父亲 4 for(int i=last[x];i;i=e[i].pre) 5 { 6 int y=e[i].to; 7 if(y==f)continue; 8 deep[y]=deep[x]+1;//记录深度 9 dfs1(y,x); 10 size[x]+=size[y];//更新以x为根节点的子树大小 11 if(size[y]>size[son[x]])son[x]=y;//更新重儿子 12 } 13 }
因为我们要在链上维护线段树(这里以线段树为例,当然其他也行)
怎么做呢,不可能每一条链都维护一个吧,绝对会MLE
那我们可以让每一条重链的编号都是有序的,这样在线段树上做操作就很方便
怎么才能有序呢?每次按照重儿子搜索就好啦
1 void dfs2(int x,int topf){ 2 id[x]=++cnt;//记录编号 3 top[x]=topf;//记录链的顶端,方便后面跳 4 v[cnt]=a[x];//保存节点数据 5 if(!son[x])return;//没有儿子就返回 6 dfs2(son[x],topf);//先搜索重儿子,保证重链上面的编号是连续的 7 for(int i=last[x];i;i=e[i].pre) 8 { 9 int y=e[i].to; 10 if(y==fa[x]||y==son[x])continue;//搜索其他儿子 11 dfs2(y,y);//因为不是重儿子了,重链断了,所以重新开一条链 12 } 13 }
预处理完了,然后来康康操作啊
操作 1: 格式: x y z 表示将树从 x 到 y 结点最短路径上所有节点的值都加上 z。
树上最短路径想到什么,LCA啊。但是我们这里不用倍增啥的,因为有轻重链,所以找这个只是log级别的,所以每次一条一条的跳就行了,至于途中的操作,就由线段树实现啦
void upG(int x,int y,int k){ while(top[x]!=top[y])//不在同一条链 { if(deep[top[x]]//保证x深度更大 ADD(id[top[x]],id[x],1,n,1,k);//线段树维护 x=fa[top[x]];//跳链上去, } if(deep[x]<deep[y])swap(x,y); ADD(id[y],id[x],1,n,1,k);//剩下的直接跳 维护 }
操作 2: 格式: x y表示求树从 x 到 y 结点最短路径上所有节点的值之和。
这个,和上面的操作差不多啊,只是线段树的操作改了一下
1 int getG(int x,int y){ 2 int res=0; 3 while(top[x]!=top[y])//和上面一样的操作 4 { 5 if(deep[top[x]]<deep[top[y]])swap(x,y); 6 res+=getsum(id[top[x]],id[x],1,n,1);//线段树解决这条链 7 x=fa[top[x]]; 8 } 9 if(deep[x]<deep[y])swap(x,y); 10 res+=getsum(id[y],id[x],1,n,1); 11 return res; 12 }
操作 3: 格式: x z 表示将以 x 为根节点的子树内所有节点值都加上 z。
首先,我们如何确定这颗子树的编号是否是连续的呢,以及如何求到这颗子树的范围呢?
因为我们是深搜,所以一个根节点的子树一定都在他的后面被搜索到,并且中途一定不会跳到其他地方,所以最后一个的编号是根节点编号+子树节点数-1(减掉自己)
1 void upSON(int x,int k){ 2 ADD(id[x],id[x]+size[x]-1,1,n,1,k);//根节点编号和最后一个编号 3 }
操作 4: 格式: x 表示求以 x 为根节点的子树内所有节点值之和
1 int getSON(int x){ 2 return getsum(id[x],id[x]+size[x]-1,1,n,1);//这个也还是一样的 3 }
线段树操作
其实和板子差不多,就是由几个小细节
比如存节点信息的时候要记得是剖分后的数组,还有一些细节的地方比如左右区间啊
1 #include2 #define int long long 3 #define ls(p) (p<<1) 4 #define rs(p) (p<<1|1) 5 using namespace std; 6 const int N=100010; 7 const int M=500010; 8 int n,m,r,mod; 9 int cnt; 10 int a[N],last[N]; 11 int son[N],deep[N],size[N],fa[N]; 12 int id[N],top[N],v[N]; 13 int ans[4*N],tag[4*N];//线段树要开大点 14 struct node{ 15 int pre,to; 16 }e[2*M]; 17 inline int read(){ 18 int x=0,f=1; 19 char ch=getchar(); 20 while(ch<'0'||ch>'9'){ 21 if(ch=='-') 22 f=-1; 23 ch=getchar(); 24 } 25 while(ch>='0'&&ch<='9'){ 26 x=(x<<1)+(x<<3)+(ch^48); 27 ch=getchar(); 28 } 29 return x*f; 30 } 31 void add(int x,int y){//链式前向星 32 cnt++; 33 e[cnt].pre=last[x]; 34 e[cnt].to=y; 35 last[x]=cnt; 36 } 37 //-----------------------------------------线段树 38 void push_up(int p){ 39 ans[p]=ans[ls(p)]+ans[rs(p)];//维护节点 40 } 41 inline void f(int p,int l,int r,int k){//向下传递 42 tag[p]+=k; 43 ans[p]+=k*(r-l+1); 44 return; 45 } 46 void push_down(int p,int l,int r){//向下传递 47 int mid=(l+r)>>1; 48 f(ls(p),l,mid,tag[p]); 49 f(rs(p),mid+1,r,tag[p]); 50 tag[p]=0; 51 return; 52 } 53 void ADD(int nl,int nr,int l,int r,int p,int k){//要操作的左右区间和当前左右区间和编号和要变化的信息 54 if(nl<=l&&r<=nr) 55 { 56 ans[p]+=k*(r-l+1); 57 tag[p]+=k; 58 return; 59 } 60 push_down(p,l,r); 61 int mid=(l+r)>>1; 62 if(mid>=nl)ADD(nl,nr,l,mid,ls(p),k); 63 if(mid 1,r,rs(p),k); 64 push_up(p); 65 } 66 void build(int p,int l,int r){//当前点和左右区间 67 if(l==r) 68 { 69 ans[p]=v[l];//一定是剖分后的数组 70 return; 71 } 72 int mid=(l+r)>>1; 73 build(ls(p),l,mid); 74 build(rs(p),mid+1,r); 75 push_up(p); 76 return ; 77 } 78 int getsum(int nl,int nr,int l,int r,int p){ 79 if(nl<=l&&r<=nr) 80 return ans[p]; 81 push_down(p,l,r); 82 int mid=(l+r)>>1,res=0; 83 if(mid>=nl)res+=getsum(nl,nr,l,mid,ls(p)); 84 if(mid 1,r,rs(p)); 85 return res; 86 } 87 //---------------------------------树链剖分 88 void dfs1(int x,int f){ 89 size[x]=1; 90 fa[x]=f; 91 for(int i=last[x];i;i=e[i].pre) 92 { 93 int y=e[i].to; 94 if(y==f)continue; 95 deep[y]=deep[x]+1; 96 dfs1(y,x); 97 size[x]+=size[y]; 98 if(size[y]>size[son[x]])son[x]=y; 99 } 100 } 101 void dfs2(int x,int topf){ 102 id[x]=++cnt; 103 top[x]=topf; 104 v[cnt]=a[x]; 105 if(!son[x])return; 106 dfs2(son[x],topf); 107 for(int i=last[x];i;i=e[i].pre) 108 { 109 int y=e[i].to; 110 if(y==fa[x]||y==son[x])continue; 111 dfs2(y,y); 112 } 113 } 114 void upG(int x,int y,int k){ 115 while(top[x]!=top[y]) 116 { 117 if(deep[top[x]]<deep[top[y]])swap(x,y); 118 ADD(id[top[x]],id[x],1,n,1,k); 119 x=fa[top[x]]; 120 } 121 if(deep[x]<deep[y])swap(x,y); 122 ADD(id[y],id[x],1,n,1,k); 123 } 124 void upSON(int x,int k){ 125 ADD(id[x],id[x]+size[x]-1,1,n,1,k); 126 } 127 int getG(int x,int y){ 128 int res=0; 129 while(top[x]!=top[y]) 130 { 131 if(deep[top[x]]<deep[top[y]])swap(x,y); 132 res+=getsum(id[top[x]],id[x],1,n,1); 133 x=fa[top[x]]; 134 } 135 if(deep[x]<deep[y])swap(x,y); 136 res+=getsum(id[y],id[x],1,n,1); 137 return res; 138 } 139 int getSON(int x){ 140 return getsum(id[x],id[x]+size[x]-1,1,n,1); 141 } 142 //--------------------------------- 143 144 signed main() 145 { 146 n=read(),m=read(),r=read(),mod=read(); 147 for(int i=1;i<=n;i++)a[i]=read(); 148 for(int i=1;i ) 149 { 150 int x,y; 151 x=read(),y=read(); 152 add(x,y),add(y,x); 153 } 154 cnt=0; 155 dfs1(r,0); 156 dfs2(r,r); 157 build(1,1,n); 158 while(m--) 159 { 160 int k,x,y,z; 161 k=read(); 162 switch(k) 163 { 164 case 1:{ 165 x=read(),y=read(),z=read(); 166 upG(x,y,z); 167 break; 168 } 169 case 2:{ 170 x=read(),y=read(); 171 printf("%lld\n",getG(x,y)%mod); 172 break; 173 } 174 case 3:{ 175 x=read(),y=read(); 176 upSON(x,y); 177 break; 178 } 179 case 4:{ 180 x=read(); 181 printf("%lld\n",getSON(x)%mod); 182 break; 183 } 184 } 185 } 186 return 0; 187 }
(注意我的define,不开long long 见祖宗)
理解不是很难,但是出错率很高,要经常训练才行