树链剖分(1)
---恢复内容开始---
对于一个树的图,有如下概念
- 重结点:子树结点数目最多的结点;
- 轻节点:父亲节点中除了重结点以外的结点;
- 重边:父亲结点和重结点连成的边;
- 轻边:父亲节点和轻节点连成的边;
- 重链:由多条重边连接而成的路径;
- 轻链:由多条轻边连接而成的路径;
基本原理,就是对于一组树形的数据结构的操作区间取值,采用将树分成链,然后利用数据结构(线段树、树状数组等)来维护这些链。
siz[u] | 保存以u为根的子树节点个数 dfs1 |
top[u] | 保存当前节点所在链的顶端节点 dfs2 |
son[u] | 保存重儿子 dfs1 |
dep[u] | 保存结点u的深度值 dfs1 |
faz[u] | 保存结点u的父亲节点 dfs1 |
tid[u] | 保存树中每个节点剖分以后的新编号(DFS的执行顺序) dfs2 |
rnk[u] | 保存当前节点在树中的位置 dfs2 |
还有两条性质:
- 如果(u, v)是一条轻边,那么size(v) < size(u) / 2; ps:因为重点是子节点数最多的了
- 从根结点到任意结点的路所经过的轻重链的个数必定都小与O(logn);
再次想一想我们需要什么
***节点的重儿子son数组
***该节点的父亲f数组
***节点的根节点top数组
***该节点的子节点个数siz数组
***该节点的深度dep数组
***dfs中该节点被访问的顺序did数组
***dfs中访问顺序所对应的节点
两次dfs
维护线段树
进行LCA查询操作
如果改查一个节点和其子树————dfs序查询一个节点和其子树————dfs序 + 线段树维护
如果改查一条链或者不同链上的任意两个点,线段树就不够了需要用到LCA并且利用top数组实现优化,快速计算
初始准备工作:
//https://www.luogu.org/problemnew/show/P3384 //https://www.cnblogs.com/George1994/p/7821357.html #include <iostream> #include <cstdio> #include <string.h> #include <cmath> #include <algorithm> #define inf (1 << 28) #define lson rt<<1,left,mid #define rson rt<<1|1,mid+1,right #define ls rt<<1 #define rs rt<<1|1 using namespace std; typedef long long ll; int n,m,root,Mod;//节点个数,操作个数,根节点序号,取模数 const int maxn = 1e5 + 1e3; int V[maxn]; //step1 构建线段树 ll lazy[maxn<<2];//经典之lazy标记 ll val[maxn<<2];//树中的节点对应的值(树中的节点就是dfs的序号) /*dfs1*/ int siz[maxn]; int dep[maxn]; int son[maxn]; int fa[maxn]; /*dfs2*/ int did[maxn];//dfs序对应的原数据的节点值 --- 通过节点得知dfs序号 int rnk[maxn];//这个节点的值对应的dfs序 --- 通过序号得知节点号 int top[maxn]; int tot; struct node{ int to,pre; }e[maxn << 1]; int id[maxn],cnt;
两次dfs得到我们所需要的东西
void dfs1(int rt,int f,int depth) { fa[rt] = f; dep[rt] = depth; siz[rt] = 1; for(int i = id[rt];~i;i = e[i].pre) { int to = e[i].to; if(to != f) { dfs1(to,rt,depth+1); siz[rt] += siz[to]; if(siz[to] > siz[son[rt]]) son[rt] = to; } } } void dfs2(int now,int rt) { top[now] = rt; did[now] = ++tot; rnk[tot] = now; if(son[now]) dfs2(son[now],rt); else return; for(int i = id[now];~i;i = e[i].pre) { int to = e[i].to; if(to != fa[now] && to != son[now]) { dfs2(to,to); } } }
然后对一些必要数据的初始化
void init() { memset(id,-1,sizeof(id)); memset(siz,0,sizeof(siz)); memset(son,0,sizeof(son)); cnt = 0; tot = 0; } void add(int from,int to) { e[cnt].to = to; e[cnt].pre = id[from]; id[from] = cnt++; }
然后针对线段树的常规操作
void pup(int rt) { val[rt] = val[ls] + val[rs]; } void build(int rt,int left,int right) { lazy[rt] = 0; if(left == right) val[rt] = V[rnk[left]]; else { int mid = (left + right) >> 1; build(lson); build(rson); pup(rt); } /* PS:rt就是rt,我要访问也是通过left和right来访问操作,所以不必如此 */ } void pdown(int rt,int left,int right) { if(lazy[rt]) { int lt = lazy[rt]; int mid = (left + right) >> 1; val[ls] += (mid - left + 1) * lt; val[rs] += (right - mid) * lt; lazy[rs] += lt; lazy[ls] += lt; lazy[rt] = 0; } } void update(int rt,int left,int right,int l,int r,ll k) { if(l <= left && right <= r) { val[rt] += (right - left + 1) * k; lazy[rt] += k; return; } if(left > r || right < l)return; pdown(rt,left,right); int mid = (left + right) >> 1; if(left <= mid) update(lson,l,r,k); if(right > mid) update(rson,l,r,k); pup(rt); } ll query(int rt,int left,int right,int l,int r) { ll res = 0; if(l <= left && right <= r) { return val[rt]; } if(left > r || right < l)return 0 ; pdown(rt,left,right); int mid = (left + right) >> 1; if(left <= mid) res += query(lson,l,r); res %= Mod; if(right > mid) res += query(rson,l,r); return res % Mod; }
最主要的就是LCA查询操作
线段树可以提供查询连续的区间值,但如果区间不连续,我们可以利用LCA将深度低的点利用top数组往上跳,记录中间的权值
一旦在一条链上则直接利用线段树,改查都是这样的思想
ll query_lca(int x,int y) { ll res = 0; while(top[x] != top[y]) { if(dep[top[x]] < dep[top[y]])swap(x,y); res += query(1,1,tot,did[top[x]],did[x]); x = fa[top[x]]; res %= Mod; } if(dep[x] < dep[y])swap(x,y); res += query(1,1,tot,did[y],did[x]); return res % Mod; } void updata_lca(int x,int y,int z) { while(top[x] != top[y]) { if(dep[top[x]] < dep[top[y]])swap(x,y); update(1,1,tot,did[top[x]],did[x],z); x = fa[top[x]]; } if(dep[x] < dep[y])swap(x,y); update(1,1,tot,did[y],did[x],z); }
最后就简单略
int main() { while(~scanf("%d%d%d%d",&n,&m,&root,&Mod)) { init(); for(int i = 1;i <= n;i++ ) { scanf("%d",&V[i]); } int from, to; for(int i = 0;i < n-1;i++) { scanf("%d%d",&from,&to); add(from,to); add(to,from); } dfs1(root,root,1); dfs2(root,root); //cout<<tot<<endl;对 build(1,1,tot); int op,x,y,z; for(int i = 0;i < m;i++) { scanf("%d",&op); if(op == 1) { scanf("%d%d%d",&x,&y,&z); updata_lca(x,y,z); } else if(op == 2) { scanf("%d%d",&x,&y); printf("%lld\n",query_lca(x,y)); } else if(op == 3) { scanf("%d%d",&x,&z); update(1,1,tot,did[x],did[x]+siz[x] - 1,z); } else if(op == 4) { scanf("%d",&x); printf("%lld\n",query(1,1,tot,did[x],did[x] + siz[x] - 1)); } } } return 0; }
PS所要注意一点的是 longlong的情况,都要考虑到略,别有的地方longlong了有的地方没有longlong