树链剖分学习日记
树链剖分
【11月27日星期二晴】
先【下认识树链剖分的概念】
从大佬处摘录而来,先认识下概念。
树链剖分就是对一棵树分成几条链,把树形变为线性,减少处理难度
需要处理的问题:
- 将树从X到ý结点最短路径上所有节点的值都加上ž
- 求树从X到ý结点最短路径上所有节点的值之和
- 将以X为根节点的子树内所有节点值都加上ž
- 求以X为根节点的子树内所有节点值之和
概念
- 重儿子:对于每一个非叶子节点,它的儿子中以那个儿子为根的子树节点数最大的儿子为该节点的重儿子
- 轻儿子:对于每一个非叶子节点,它的儿子中非重儿子的剩下所有儿子即为轻儿子
- 叶子节点没有重儿子也没有轻儿子(因为它没有儿子..)
- 重边:一个父亲连接他的重儿子的边称为重边(由于考虑到父节点的情况所以不能看作任意两重儿子的链接边)
- 轻边:剩下的即为轻边
- 重链:相邻重边连起来的连接一条重儿子的链叫重链 对于叶子节点,若其为轻儿子,则有一条以自己为起点的长度为1的链
- 每一条重链以轻儿子为起点
DFS1()
这个DFS要处理几件事情:
- 标记每个点的深度DEP []
- 标记每个点的父亲FA []
- 标记每个非叶子节点的子树大小(含它自己)
- 标记每个非叶子节点的重儿子编号儿子[]
[code]inline void dfs1(int x,int f,int deep){//x当前节点,f父亲,deep深度 dep[x]=deep;//标记每个点的深度 fa[x]=f;//标记每个点的父亲 siz[x]=1;//标记每个非叶子节点的子树大小 int maxson=-1;//记录重儿子的儿子数 for(Rint i=beg[x];i;i=nex[i]){ int y=to[i]; if(y==f)continue;//若为父亲则continue dfs1(y,x,deep+1);//dfs其儿子 siz[x]+=siz[y];//把它的儿子数加到它身上 if(siz[y]>maxson)son[x]=y,maxson=siz[y];//标记每个非叶子节点的重儿子编号 } }//变量解释见最下面
DFS2()
这个DFS2也要预处理几件事情
- 标记每个点的新编号
- 赋值每个点的初始值到新编号上
- 处理每个点所在链的顶端
- 处理每条链
顺序:先处理重儿子再处理轻儿子,理由后面说
[code]inline void dfs2(int x,int topf){//x当前节点,topf当前链的最顶端的节点 id[x]=++cnt;//标记每个点的新编号 wt[cnt]=w[x];//把每个点的初始值赋到新编号上来 top[x]=topf;//这个点所在链的顶端 if(!son[x])return;//如果没有儿子则返回 dfs2(son[x],topf);//按先处理重儿子,再处理轻儿子的顺序递归处理 for(Rint i=beg[x];i;i=nex[i]){ int y=to[i]; if(y==fa[x]||y==son[x])continue; dfs2(y,y);//对于每一个轻儿子都有一条从它自己开始的链 } }//变量解释见最下面
处理问题
注意重要的来了!!!
前面说到dfs2的顺序是先处理重儿子再处理轻儿子
我们来模拟一下:
- 因为顺序是先重再轻,所以每一条重链的新编号是连续的
- 因为是DFS,所以每一个子树的新编号也是连续的
现在回顾一下我们要处理的问题
- 处理任意两点间路径上的点权和
- 处理一点及其子树的点权和
- 修改任意两点间路径上的点权
- 修改一点及其子树的点权
如图1所示,当我们要处理任意两点间路径时:
设所在链顶端的深度更深的那个点为X点
- ans加上x点到x所在链顶端这一段区间的点权和
- 把X跳到X所在链顶端的那个点的上面一个点
不停执行这两个步骤,直到两个点处于一条链上,这时再加上此时两个点的区间和即可
这时我们注意到,我们所要处理的所有区间均为连续编号(新编号),于是想到线段树,线段用树处理连续编号区间状语从句:
每次查询时间复杂度为O(log2n)O(log2n )
[code]inline int qRange(int x,int y){ int ans=0; while(top[x]!=top[y]){//当两个点不在同一条链上 if(dep[top[x]]<dep[top[y]])swap(x,y);//把x点改为所在链顶端的深度更深的那个点 res=0; query(1,1,n,id[top[x]],id[x]);//ans加上x点到x所在链顶端 这一段区间的点权和 ans+=res; ans%=mod;//按题意取模 x=fa[top[x]];//把x跳到x所在链顶端的那个点的上面一个点 } //直到两个点处于一条链上 if(dep[x]>dep[y])swap(x,y);//把x点深度更深的那个点 res=0; query(1,1,n,id[x],id[y]);//这时再加上此时两个点的区间和即可 ans+=res; return ans%mod; }//变量解释见最下面
2,处理一点及其子树的点权和:
想到记录了每个非叶子节点的子树大小(含它自己),每个并且子树的新编号都是连续的
于是直接线段树区间查询即可
时间复杂度为O(logN)的O(logn)
[code]inline int qSon(int x){ res=0; query(1,1,n,id[x],id[x]+siz[x]-1);//子树区间右端点为id[x]+siz[x]-1 return res; }
当然,区间修改就和区间查询一样的啦~~
[code]inlin 3ff7 e void updRange(int x,int y,int k){ k%=mod; while(top[x]!=top[y]){ if(dep[top[x]]<dep[top[y]])swap(x,y); update(1,1,n,id[top[x]],id[x],k); x=fa[top[x]]; } if(dep[x]>dep[y])swap(x,y); update(1,1,n,id[x],id[y],k); } inline void updSon(int x,int k){ update(1,1,n,id[x],id[x]+siz[x]-1,k); }//变量解释见最下面
建树
既然前面说到要用线段树,那么按题意建树就可以了。
此后,看了篇数链剖分的模板,还是可以的就是SPOJ375的例子:代码链接,贴上去的。
【现在是晚上9:40了,学习了一整天,然后把洛谷的P3384给过了】
学习了一整天,也用掉了好几张草稿纸去模拟树链剖分的运行方式,挺好的,刚找到BUG然后过掉了。
就以洛谷的这道题来讲一下吧,(打个广告:树链剖分习题就要上线啦!!!(11月28日上午10时正式开始,欢迎捧场)),我们要有这么几种操作和这么几种数组的相关作用:
- w[]数组:用以记录原来所对应的每个点的价值;
- head[]:用以链式前向星使用;
- depth[]:记录每个节点距离根节点的深度,我令根节点从0开始;
- root[]:记录每个节点的父节点,到时候方便我处理不在同一个重链上的查询区间(跟LCA的逼近法相似);
- size[]:记录每个节点,以它为根节点,其子节点个数(包含自己)——用以处理该根节点下的更新与求和之用;
- W_son[]:记录每个节点的重儿子节点,叶子节点的W_son为0;
- id[]:每个节点在dfs2()过后生成的在树上的对应位置,此处在同一个链上的节点应该是连续的;
- top[]:记录每个节点的所在重链的最顶根节点;
- tree[]和lazy[]:这大家都懂得吧,线段树嘛。
dfs1():
- 处理深度;
- 找父节点;
- 它下面有多少节点;
- 谁是它的重儿子;
- size[]求和。
dfs2():
- 重边变成重链;
- 将原来的节点按序拍上树去,就是赋予连续值,就叫做赋新值吧;
- 重儿子传递重链;
- 轻儿子以它为根,构建新重链。
Query_range():
- 首先处理不在同链上的节点,先找出他们的值,使两查询数在统一链上;
- 统一链了之后,就是从上往下线段树query()即可。
Query_son():
- 查以该节点为根的节点总权值和,其实就是x节点之下的包括其本身的所有节点和,不就是x~x+size[x]-1嘛。
上个代码给大家看看,学习一天的成果,个人感觉美滋滋:
[code]#include <iostream> #include <cstdio> #include <cmath> #include <string> #include <cstring> #include <algorithm> #include <limits> #include <vector> #include <stack> #include <queue> #include <set> #include <map> #define lowbit(x) ( x&(-x) ) #define pi 3.141592653589793 #define e 2.718281828459045 using namespace std; typedef unsigned long long ull; typedef long long ll; const int maxN = 4e5+5; int N, Q, R, Mod, w[maxN], head[maxN], cnt, depth[maxN], root[maxN], size[maxN], W_son[maxN], id[maxN], num, new_W[maxN], top[maxN], tree[maxN<<2], lazy[maxN<<2]; struct Eddge { int nex, to; Eddge(int a=-1, int b=0):nex(a), to(b) {} }edge[maxN]; void addEddge(int u, int v) { edge[cnt] = Eddge(head[u], v); head[u] = cnt++; } void dfs1(int u, int fa, int deep) { depth[u] = deep; root[u] = fa; size[u] = 1; int maxSon = -1; for(int i=head[u]; i!=-1; i=edge[i].nex) { int v = edge[i].to; if(v == fa) continue; dfs1(v, u, deep+1); size[u] += size[v]; if(size[v] > maxSon) { maxSon = size[v]; W_son[u] = v; } } } void dfs2(int u, int topf) { id[u] = ++num; new_W[num] = w[u]; top[u] = topf; if(!W_son[u]) return; dfs2(W_son[u], topf); for(int i=head[u]; i!=-1; i=edge[i].nex) { int v = edge[i].to; if(v == W_son[u] || v == root[u]) continue; dfs2(v, v); } } void buildTree(int rt, int l, int r) { lazy[rt] = 0; if(l == r) { tree[rt] = new_W[l]; return; } int mid = (l + r)>>1; buildTree(rt<<1, l, mid); buildTree(rt<<1|1, mid+1, r); tree[rt] = ( tree[rt<<1] + tree[rt<<1|1] )%Mod; } void pushdown(int rt, int l, int r) { if(lazy[rt]) { int mid = (l + r)>>1; lazy[rt<<1] += lazy[rt]; lazy[rt<<1]%=Mod; lazy[rt<<1|1] += lazy[rt]; lazy[rt<<1|1]%=Mod; tree[rt<<1] += lazy[rt]*(mid - l + 1)%Mod; tree[rt<<1]%=Mod; tree[rt<<1|1] += lazy[rt]*(r - mid)%Mod; tree[rt<<1|1]%=Mod; lazy[rt] = 0; } } void update(int rt, int l, int r, int ql, int qr, int val) { if(ql<=l && qr>=r) { lazy[rt] += val; tree[rt] = ( tree[rt] + val*(r-l+1) )%Mod; return; } pushdown(rt, l, r); int mid = (l + r)>>1; if(ql>mid) update(rt<<1|1, mid+1, r, ql, qr, val); else if(qr<=mid) update(rt<<1, l, mid, ql, qr, val); else { update(rt<<1|1, mid+1, r, ql, qr, val); update(rt<<1, l, mid, ql, qr, val); } tree[rt] = ( tree[rt<<1] + tree[rt<<1|1] )%Mod; } int query(int rt, int l, int r, int ql, int qr) { if(ql<=l && qr>=r) return tree[rt]; pushdown(rt, l, r); int mid = (l + r)>>1; if(ql>mid) return query(rt<<1|1, mid+1, r, ql, qr); else if(qr<=mid) return query(rt<<1, l, mid, ql, qr); else { int ans = query(rt<<1|1, mid+1, r, ql, qr); ans = (ans + query(rt<<1, l, mid, ql, qr))%Mod; return ans; } } void update_range(int x, int y, int val) { while(top[x] != top[y]) { if(depth[top[x]] < depth[top[y]]) swap(x, y); update(1, 1, N, id[top[x]], id[x], val); x = root[top[x]]; } if(depth[x] > depth[y]) swap(x, y); update(1, 1, N, id[x], id[y], val); } int Query_range(int x, int y) { int ans = 0; while(top[x] != top[y]) { if(depth[top[x]] < depth[top[y]]) swap(x, y); ans = (ans + query(1, 1, N, id[top[x]], id[x]))%Mod; x = root[top[x]]; } if(depth[x] > depth[y]) swap(x, y); ans = (ans + query(1, 1, N, id[x], id[y]))%Mod; return ans; } void update_son(int x, int val) { update(1, 1, N, id[x], id[x]+size[x]-1, val); } int Query_son(int x) { return query(1, 1, N, id[x], id[x]+size[x]-1); } void init() { cnt = num = 0; memset(head, -1, sizeof(head)); memset(W_son, 0, sizeof(W_son)); } int main() { while(scanf("%d%d%d%d", &N, &Q, &R, &Mod)!=EOF) { init(); for(int i=1; i<=N; i++) scanf("%d", &w[i]); for(int i=1; i<N; i++) { int e1, e2; scanf("%d%d", &e1, &e2); addEddge(e1, e2); addEddge(e2, e1); } dfs1(R, R, 0); dfs2(R, R); buildTree(1, 1, N); while(Q--) { int op, x, y, z; scanf("%d", &op); if(op == 1) { scanf("%d%d%d", &x, &y, &z); update_range(x, y, z); } else if(op == 2) { scanf("%d%d", &x, &y); printf("%d\n", Query_range(x, y)); } else if(op == 3) { scanf("%d%d", &x, &z); update_son(x, z); } else { scanf("%d", &x); printf("%d\n", Query_son(x)); } } } return 0; }
【11月28日 星期三 晴】
今天补题,过掉了牛客上之前小白赛(神仙场)的D题树上求和——树链剖分,这道题与模板题不一样了,但其实没多大差别,就是推一个求子节点平方和的公式X1^2+X2^2+......+Xn^2;有(x+y)^2=X^2+Y^2+2xy,那么多几个x、y也是成立的:(X1+X2+X3+......+Xn+Y)=X1^2+X2^2+......+Xn^2+2*(X1+X2+X3+......+Xn)*Y+Y^2。
那么,上代码吧:
[code]#include <iostream> #include <cstdio> #include <cmath> #include <string> #include <cstring> #include <algorithm> #include <limits> #include <vector> #include <stack> #include <queue> #include <set> #include <map> #define lowbit(x) ( x&(-x) ) #define pi 3.141592653589793 #define e 2.718281828459045 using namespace std; typedef unsigned long long ull; typedef long long ll; const int mod = 23333; const int maxN = 100010; int N, Q, w[maxN], head[maxN], cnt, depth[maxN], root[maxN], size[maxN], W_son[maxN], top[maxN], id[maxN], num; ll new_W[maxN], sum[maxN<<2], multi[maxN<<2], lazy[maxN<<2]; struct Eddge { int nex, to; Eddge(int a=-1, int b=0):nex(a), to(b) {} }edge[maxN<<1]; void addEddge(int u, int v) { edge[cnt] = Eddge(head[u], v); head[u] = cnt++; } void dfs1(int u, int fa, int deep) { root[u] = fa; depth[u] = deep; size[u] = 1; int maxSon = -1; for(int i=head[u]; i!=-1; i=edge[i].nex) { int v = edge[i].to; if(v == fa) continue; dfs1(v, u, deep+1); size[u] += size[v]; if(size[v] > maxSon) { maxSon = size[v]; W_son[u] = v; } } } void dfs2(int x, int topf) { top[x] = topf; id[x] = ++num; new_W[num] = w[x]; if(!W_son[x]) return; dfs2(W_son[x], topf); for(int i=head[x]; i!=-1; i=edge[i].nex) { int v = edge[i].to; if(v == W_son[x] || v == root[x]) continue; dfs2(v, v); } } void pushup(int rt) { sum[rt] = ( sum[rt<<1] + sum[rt<<1|1] )%mod; multi[rt] = ( multi[rt<<1] + multi[rt<<1|1] )%mod; } void buildTree(int rt, int l, int r) { lazy[rt] = 0; if(l == r) { sum[rt] = new_W[l]%mod; multi[rt] = sum[rt] * sum[rt] %mod; return; } int mid = (l + r)>>1; buildTree(rt<<1, l, mid); buildTree(rt<<1|1, mid+1, r); pushup(rt); } void pushdown(int rt, int l, int r) { if(lazy[rt]) { lazy[rt]%=mod; lazy[rt<<1] = ( lazy[rt<<1] + lazy[rt] )%mod; lazy[rt<<1|1] = ( lazy[rt<<1|1] + lazy[rt] )%mod; int mid = (l + r)>>1; multi[rt<<1] = ( multi[rt<<1] + 2*sum[rt<<1]*lazy[rt]%mod + lazy[rt]*lazy[rt]%mod*(mid - l + 1)%mod )%mod; multi[rt<<1|1] = ( multi[rt<<1|1] + 2*sum[rt<<1|1]*lazy[rt]%mod + lazy[rt]*lazy[rt]%mod*(r - mid)%mod )%mod; sum[rt<<1] = ( sum[rt<<1] + (mid - l + 1)*lazy[rt] )%mod; sum[rt<<1|1] = ( sum[rt<<1|1] + (r - mid)*lazy[rt] )%mod; lazy[rt] = 0; } } void update(int rt, int l, int r, int ql, int qr, ll val) { if(ql<=l && qr>=r) { lazy[rt] = ( lazy[rt] + val )%mod; multi[rt] = ( multi[rt] + 2*sum[rt]*val%mod + val*val%mod*(r - l + 1)%mod ); sum[rt] = ( sum[rt] + val*(r - l + 1)%mod )%mod; return; } pushdown(rt, l, r); int mid = (l + r)>>1; if(ql>mid) update(rt<<1|1, mid+1, r, ql, qr, val); else if(qr<=mid) update(rt<<1, l, mid, ql, qr, val); else { update(rt<<1, l, mid, ql, qr, val); update(rt<<1|1, mid+1, r, ql, qr, val); } pushup(rt); } ll query(int rt, int l, int r, int ql, int qr) { if(ql<=l && qr>=r) return multi[rt]%mod; pushdown(rt, l, r); int mid = (l + r)>>1; if(ql>mid) return query(rt<<1|1, mid+1, r, ql, qr); else if(qr<=mid) return query(rt<<1, l, mid, ql, qr); else { ll ans = query(rt<<1, l, mid, ql, mid); ans = ( ans + query(rt<<1|1, mid+1, r, mid+1, qr) )%mod; return ans; } } void update_Son(int x, ll val) { val%=mod; update(1, 1, N, id[x], id[x]+size[x]-1, val); } ll query_Son(int x) { return query(1, 1, N, id[x], id[x]+size[x]-1); } void init() { memset(head, -1, sizeof(head)); cnt = num = 0; memset(W_son, 0, sizeof(W_son)); } int main() { while(scanf("%d%d", &N, &Q)!=EOF) { init(); for(int i=1; i<=N; i++) scanf("%d", &w[i]); for(int i=1; i<N; i++) { int e1, e2; scanf("%d%d", &e1, &e2); addEddge(e1, e2); addEddge(e2, e1); } dfs1(1, 1, 0); dfs2(1, 1); buildTree(1, 1, N); while(Q--) { int op, x, y; scanf("%d", &op); if(op == 1) { scanf("%d%d", &x, &y); update_Son(x, y); } else { scanf("%d", &x); printf("%lld\n", query_Son(x)); } } } return 0; }
感觉还不错哦,对于DFS1()以及DFS2()有了更深的了解,多做题吧,还有一套,欢迎各大牛前来AK。
- 黑马程序员_学习日记14_System命名空间
- c++学习日记 1
- 【Python学习日记】函数列表 动态创建函数 文件读取 迭代器
- Android学习日记——安全
- Egret入门学习日记 --- 第一篇
- maven学习日记一 环境搭建配合eclipse使用
- Android学习日记(yzy):Broadcast Receiver的注册和发送
- 黑马程序员_JAVA学习日记_JAVAJDK1.5以后高新技术
- Arduino学习日记一
- 黑马程序员 日记二:线程的互斥的学习
- 黑马程序员学习日记(10)--socket服务端与客户端信息发送(2)
- 开始学习写日记
- java学习日记_18:面向对象之封装的好处和原则。07.05
- Laravel5.4 学习日记
- Python 学习日记第一篇 -- 数字,字符串
- SQLCookBook第一章学习日记2
- 黑马程序员-我的第十三篇学习日记:OC中dealloc方法和@property作用
- Ethercat学习日记
- 场景识别学习笔记日记2016.11.24
- 学习日记-Bootstrap HTML编码规范