树上问题
主要是根据学长的课件来透彻的。所以好多地方直接粘过来了qwq
树链剖分
所谓树链剖分,就是将树上的边进行划分。
树链剖分有重链剖分,长链剖分,实链剖分等等。
长链剖分是用来\(O(1)\)求\(k\)级祖先的,和优化一些树形DP,具体地来说是一些跟深度有关的DP。
实链剖分是我们常说的\(LCT(Link-Cut-Tree)\)。
本文介绍的主要是重链剖分。
重链剖分
既然是重链剖分,那么一定有重链和轻链,但是我们怎么来划分轻重链呢?
我们定义:一个节点的所有子节点中\(size\)最大的那个节点为重儿子;那么这两个节点之间所连的边为一条重边。
这样的话我们会得到一些性质。
性质1:从根节点到叶子节点的路径上,跳重链的次数不会超过\(O(logn)\),从叶子节点到根节点,也成立
证明:我们考虑,什么时候会跳重链,一定是当他需要向轻儿子走的时候废话。那么轻儿子的\(size\),一定小于他父亲节点
的\(\frac{1}{2}\),那么不断走轻儿子,一直乘\(\frac{1}{2}\),只需要跳\(\log n\)次,就到\(1\)了,也就是叶子节点。
首先考虑如何找到重儿子,我们知道,这个点的size就为他所有而儿子的size之和,且他儿子深度为当前点深度加1,递归的思路已经很明显了qwq。
代码:
int fa[maxn], dep[maxn], size[maxn], height_son[maxn];
vector<int> v[maxn];
inline void add(int x, int y){return (void)(v[x].push_back(y));}
void bulid_poutree(int now){
size[now] = 1;
for(int i = 0; i < v[now].size(); i ++){
int to = v[now][i];
if(dep[to]) continue;
dep[to] = dep[now] + 1;//下一点的深度为当前点深度加一
fa[to] = now;
bulid_poutree(to);
size[now] += size[to];
if(size[to] > size[height_son[now]]) height_son[now] = to;//找到他的重儿子
}
}
树剖求LCA
我们考虑,如果两个点在同一条重链上,那么肯定是深度小的是LCA。
但是如果不在呢?
我们是不是可以像倍增一下跳(也仅仅是像),跳重链,向上跳,然后一直跳到两个点在同一条重链上,再像刚才一样处理。
让谁跳呢,深度大的?
不不不,是\(top\)深度大的跳。
因为,既然两个点不在同一条重链上,那么显然,他们的\(top\),也不在同一条重链上。
那么类似于倍增,让\(top\)跳\(fa\),直到跳到同一条重链上。
深度小的是LCA。
如何往上跳,当两个点所在链的最高点不相同时他们一定不在同一条链上,根据这个条件while往上跳即可(让深度深的往上跳)
while(top[x] != top[y]){
if(dep[top[x]] < dep[top[y]]) swap(x,y);
/*......*/
x = fa[top[x]];
}
本人还不透彻,不如去写倍增LCA(其实倍增也不会qaq)
升华
前置芝士:线段树。
先上一道例题来透彻一下树链剖分吧。
好像不只是跳重链那么简单。
在这里,我们引入一个叫做\(dfs\)序的概念。
\(dfs\)序:我们在遍历整棵树时,每个节点被遍历到的时间戳(即这个节点是第几个被遍历到的)。
- 那么我们显然可以发现一颗子树内的\(dfs\)序是连续的,而且一条链上的\(dfs\)序也是连续的(可以画图理解)。
- 那么我们在询问一条路径时,就可以把这条路径分成好几条链,我们对于每条链分别统计即可。
那么我们怎么才能维护每条链的信息呢?
注意:一条链上的\(dfs\)序是连续的,那么问题转化为,如何维护一个区间的信息?
当然是线段树啊。
什么区间和,区间最值,区间覆盖,线段树简直不能再合适了。
那么是不是这道题就做完了口牙。
那么我们还可以保证我们树链剖分的时间复杂度为\(O(n\log^2n)\)了
int fa[maxn], dep[maxn], size[maxn], top[maxn], dfn[maxn], id[maxn], height_son[maxn];
/*其中dfn为该点所对应的dfs序,id为该点的dfs序所对应的自己原来的编号,top为这条重链上最高的点*/
vector<int> v[maxn];
inline void add(int x, int y){return (void)(v[x].push_back(y));}
void bulid_poutree(int now){
size[now] = 1;
for(int i = 0; i < v[now].size(); i ++){
int to = v[now][i];
if(dep[to]) continue;
dep[to] = dep[now] + 1;//下一点的深度为当前点深度加一
fa[to] = now;
bulid_poutree(to);
size[now] += size[to];
if(size[to] > size[height_son[now]]) height_son[now] = to;//找到他的重儿子
}
}
void dfs(int now, int topfa){ //找到点对应的dfs序
top[now] = topfa;
dfn[now] = ++cnt;
id[cnt] = now;
if(height_son[now]) dfs(height_son[now], topfa); //先走重链
for(int i = 0; i < v[now].size(); i ++){//走轻链
int to = v[now][i];
if(fa[now] == to or height_son[now] == to) continue;
dfs(to,to);
}
}
找到\(dfs\)序以后我们就把这个问题美滋滋的转化为了区间问题。
对于\(1,2\)操作:
对于一条路径,我们根据2.将其剖为若干条链,每条链上的\(dfs\)序是连续的,那么我们就让深度高的点往上跳,同时记录每一条链或修改每一条链即可,最后不要忘了记录或修改两点在同一条链上的时候,而且我们要知道,同一条链上的两个点,深度高的\(dfs\)序大。
对于\(3,4\)操作:
首先我们根据1.已经知道对于一颗子树上的\(dfs\)序是连续的,那么我们对子树的操作转到线段树上对\(dfn[x]\)到\(dfn[x]+size[x]-1\)进行常规的线段树修改和查询操作即可。
代码真的是又臭又难写qaq。
总结一下我提交了20次才A掉这道题时犯的各种错误主要还是我太蔡徐坤了qaq:
-
如果用链式前向星存图记得开双倍空间,注意他是无向边。
-
线段树千万记得开4倍空间开4倍空间4倍空间
指针请绕行。 -
用线段树对我们剖开的树进行维护,在构建树时要从节点1开始,注意你构建的线段树与题中给的树是木有关系的,即这样
tree.bulid_segtree(1,n,1);
-
线段树在修改和询问时一定要记得下放标记,并且要记得上传。
-
每个人的问题都不一样,还希望大家可以避免掉细节上的错误_(¦3」∠)_
接下来上代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int n, m, r, mod, cnt, a[maxn];
struct segpou{
int fa[maxn], dep[maxn], size[maxn], top[maxn], dfn[maxn], id[maxn], height_son[maxn], head[maxn];
struct egde{
int y, nxt;
}e[maxn*2];
inline void add(int x, int y){
e[++cnt].y = y, e[cnt].nxt = head[x];
head[x] = cnt;
return;
}
void bulid_poutree(int now){//构建树剖所需要的信息
size[now] = 1;
for(int i = head[now]; i; i = e[i].nxt){
int to = e[i].y;
if(dep[to]) continue;
dep[to] = dep[now] + 1;
fa[to] = now;
bulid_poutree(to);
size[now] += size[to];
if(size[to] > size[height_son[now]]) height_son[now] = to;
}
}
void dfs(int now, int topfa){//找到dfs序,且先走重边
top[now] = topfa;
dfn[now] = ++cnt;
id[cnt] = now;
if(height_son[now]) dfs(height_son[now], topfa);
for(int i = head[now]; i; i = e[i].nxt){
int to = e[i].y;
if(fa[now] == to or height_son[now] == to) continue;
dfs(to,to);
}
}
#define ls (now << 1)
#define rs (now<<1|1)
#define mid ((l+r)>>1)
struct node{
int l, r, sum, tag;
inline int get(){
return ((sum%mod)+(tag*(r-l+1)%mod))%mod;
}
}no[maxn*4];
void up(int now){
no[now].sum = ((no[ls].get()%mod) + (no[rs].get()%mod))%mod;
return;
}
void down(int now){
no[ls].tag += no[now].tag;
no[rs].tag += no[now].tag;
no[now].tag = 0;
return;
}
void bulid_segtree(int l, int r, int now){
no[now].l = l, no[now].r = r;
if(l == r) return (void)(no[now].sum = a[id[l]]);
bulid_segtree(l,mid,ls), bulid_segtree(mid+1,r,rs);
up(now);
}
void chenge(int l, int r, int now, int val){
if(r < no[now].l or no[now].r < l) return;
if(l <= no[now].l and no[now].r <= r) return(void)(no[now].tag += val);
down(now);
chenge(l,r,ls,val), chenge(l,r,rs,val);
up(now);
}
void query(int l, int r, int now, int &ans){
if(r < no[now].l or no[now].r < l) return;
if(l <= no[now].l and no[now].r <= r){
ans = (ans%mod+no[now].get()%mod)%mod;
return;
}
down(now);
query(l,r,ls,ans), query(l,r,rs,ans);
up(now);
}
int poutree_query(int x, int y){
/*对于两个点不在同一条链上时,我们选择让更深的点往上跳,直到在同一条链上为止*/
int res = 0;
while(top[x] != top[y]){
if(dep[top[x]] < dep[top[y]]) swap(x,y);
int tmp = 0;
query(dfn[top[x]],dfn[x],1,tmp);
res = (res%mod+tmp%mod)%mod;
x = fa[top[x]];
}
if(dep[x] < dep[y]) swap(x,y);
int tmp = 0;
query(dfn[y],dfn[x],1,tmp);
res = (res%mod+tmp%mod)%mod;
return res;
}
void poutree_chenge(int x, int y, int val){
/*修改也是如此*/
while(top[x] != top[y]){
if(dep[top[x]] < dep[top[y]]) swap(x,y);
chenge(dfn[top[x]],dfn[x],1,val);
x = fa[top[x]];
}
if(dep[x] < dep[y]) swap(x,y);
chenge(dfn[y],dfn[x],1,val);
}
}tree;
signed main(){
scanf("%d%d%d%d", &n, &m, &r, &mod);
for(int i = 1; i <= n; i ++){
scanf("%d", &a[i]);
}
for(int i = 1, x, y; i < n; i ++){
scanf("%d%d", &x, &y);
tree.add(x,y); tree.add(y,x);
}
cnt = 0, tree.dep[r] = 1;
tree.bulid_poutree(r);
tree.dfs(r,r);
tree.bulid_segtree(1,n,1);
for(int cmp, x, y, z; m; m --){
scanf("%d", &cmp);
if(cmp == 1){
scanf("%d%d%d", &x, &y, &z);
tree.poutree_chenge(x,y,z);
}
if(cmp == 2){
scanf("%d%d", &x, &y);
printf("%d\n",tree.poutree_query(x,y));
}
if(cmp == 3){
scanf("%d%d", &x, &y);
tree.chenge(tree.dfn[x],tree.dfn[x]+tree.size[x]-1,1,y);
}
if(cmp == 4){
scanf("%d", &x);
int ans = 0;
tree.query(tree.dfn[x],tree.dfn[x]+tree.size[x]-1,1,ans);
printf("%d\n", ans);
}
}
return 0;
}
树上差分
占坑qwq