树链剖分
树链剖分,是一种让你代码瞬间增加1kb,轻轻松松飙到两百行的,极其优(bào)美(lì)的毒瘤算法。
目录
一.概念
- 指一种对树进行划分的算法,它先通过轻重边剖分将树分为多条链,保证每个点属于且只属于一条链,然后再通过数据结构(树状数组、SBT、SPLAY、线段树等)来维护每一条链。
简单来说,就是:把一棵树拆成若干个不相交的链,然后用一些数据结构去维护这些链。
而我们需要解决的是,如何将树拆分成链状↓↓↓
常见的路径剖分的方法是轻重边剖分,即把树中的边分为轻重两部分;
一些需知概念:
- 重结点:子树结点数目最多的结点;
- 轻节点:父亲节点中除了重结点以外的结点;
- 重边:父亲结点和重结点连成的边;
- 轻边:父亲节点和轻节点连成的边;
- 重链:由多条重边连接而成的路径;
- 轻链:由多条轻边连接而成的路径;
给张图,大概就长介个样子:
用黑线连接的结点都是重结点,其余均是轻结点;
加粗的都是重链,其他就是轻链;
用红点标记的就是该结点所在链的起点,也就是我们提到的top结点;
用数组来描述↓↓↓
size[u] | 保存以u为根的子树节点个数 |
top[u] | 保存当前节点所在链的顶端节点 |
son[u] | 保存重儿子 |
dep[u] | 保存结点u的深度值 |
fa[u] | 保存结点u的父亲节点 |
tid[u] | 保存树中每个节点剖分以后的新编号(DFS的执行顺序) |
rnk[u] | 保存当前节点在树中的位置 |
二.操作
算法大致需要进行两次的DFS,第一次dfs:记录所有的重边,第二次dfs:连重边成重链;
第一次:
得到当前节点的父亲结点(fa数组)、当前结点的深度值(dep数组);
当前结点的子结点数量(size数组)、当前结点的重儿子(son数组);
void dfs1(int u)
{
size[u]=1;
for(int i=head[u];~i;i=e[i].start)
{
int v=e[i].end;
if(v==fa[u]) continue;
fa[v]=u;
dep[v]=dep[u]+1;
dfs1(v);
size[u]+=size[v];
if(size[v]>size[son[u]]) son[u]=v;
}
}
第二次DFS的时候则可以将各个重结点连接成重链,轻节点连接成轻链;
并且将重链(其实就是一段区间)用数据结构(一般是树状数组或线段树)来进行维护,并且为每个节点进行编号;
其实就是DFS在执行时的顺序(tid数组),以及当前节点所在链的起点(top数组),还有当前节点在树中的位置(rank数组)。
void dfs2(int u,int now)
{
top[u]=now;
rak[u]=++id;
dfn[id]=u;
if(!son[u]) return ;
dfs2(son[u],now);
for(int i=head[u];~i;i=e[i].start)
{
int v=e[i].end;
if(v!=son[u] && v!=fa[u])
{
dfs2(v,v);
}
}
}
而操作修改和查询,则以LibreOJ #139 树链剖分为例来讲
对于路径权值操作有:
- 修改路径权值:给定两个节点,将这两个节点间路径上的所有节点权值(含这两个节点)增加一个给定的值。
- 询问路径:询问某条路径上节点的权值和。
void modify_path(int x,int y,LL add)
{
int fx=top[x],fy=top[y];
while(fx!=fy)
{
if(dep[fx]<dep[fy])
{
swap(fx,fy);
swap(x,y);
}
T.update(1,n,1,rak[fx],rak[x],add);
x=fa[fx];
fx=top[x];
}
if(dep[x]>dep[y]) swap(x,y);
T.update(1,n,1,rak[x],rak[y],add);
}
LL query_path(int x,int y)
{
LL res=0;
int fx=top[x],fy=top[y];
while(fx!=fy)
{
if(dep[fx]<dep[fy])
{
swap(fx,fy);
swap(x,y);
}
res+=T.query(1,n,1,rak[fx],rak[x]);
x=fa[fx];
fx=top[x];
}
if(dep[x]>dep[y]) swap(x,y);
res+=T.query(1,n,1,rak[x],rak[y]);
return res;
}
而对于子树操作:
- 修改子树权值:给定一个节点,将以该节点为根的子树内的所有节点权值增加一个给定的值。
- 询问子树:询问某个子树内节点的权值和。
int check(int x)
{
if(x==root)return -1;
if(!(rak[x]<rak[root] && rak[x]+size[x]-1>=rak[root])) return 0;
int now=root;
while(dep[now]>dep[x])
{
if(fa[top[now]]==x)
return top[now];
now=fa[top[now]];
}
return son[x];//!!!!!!
}
void modify_tree(int x,LL y)
{
int k=check(x);
switch(k)
{
case -1:T.update(1,n,1,1,n,y);break;
case 0:T.update(1,n,1,rak[x],rak[x]+size[x]-1,y);break;
default:T.update(1,n,1,1,n,y);T.update(1,n,1,rak[k],rak[k]+size[k]-1,-y);break;
}
}
LL query_tree(int x)
{
LL res=0;
int k=check(x);
switch(k)
{
case -1:res=T.tree[1];break;
case 0:res=T.query(1,n,1,rak[x],rak[x]+size[x]-1);break;
default:res=T.tree[1]-T.query(1,n,1,rak[k],rak[k]+size[k]-1);break;
}
return res;
}
然后再用线段树维护一下就ok√
struct tre
{
LL tree[maxn<<4],lazy[maxn<<4];
void pushup(int rt)
{
tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}
void pushdown(int l,int r,int rt)
{
if(!lazy[rt]) return ;
int mid=(l+r)>>1;
tree[rt<<1]+=lazy[rt]*(mid-l+1);
tree[rt<<1|1]+=lazy[rt]*(r-mid);
lazy[rt<<1]+=lazy[rt];
lazy[rt<<1|1]+=lazy[rt];
lazy[rt]=0;
}
void buildtree(int l,int r,int rt)
{
if(l==r)
{
tree[rt]=a[dfn[l]];
return ;
}
int mid=(l+r)>>1;
buildtree(lson);
buildtree(rson);
pushup(rt);
}
void update(int l,int r,int rt,int ql,int qr,LL add)
{
int len=r-l+1;
if(ql<=l && qr>=r)
{
lazy[rt]+=add;
tree[rt]+=len*add;
return ;
}
int mid=(l+r)>>1;
pushdown(l,r,rt);
if(ql<=mid) update(lson,ql,qr,add);
if(qr>mid) update(rson,ql,qr,add);
pushup(rt);
}
LL query(int l,int r,int rt,int ql,int qr)
{
if(l>qr||r<ql)return 0;
if(ql<=l && qr>=r)
{
return tree[rt];
}
int mid=(l+r)>>1;
pushdown(l,r,rt);
LL res=0;
if(ql<=mid) res+=query(lson,ql,qr);
if(qr>mid) res+=query(rson,ql,qr);
return res;
}
}T;
至此,代码已经轻松到了两百三十多行 姨母笑/可以预见未来调试bug的悲惨情景了QAQ
三.实例
先从模板题开始啦,以后有时间再甩其他题上来otz
【 题目描述 】
给定一棵 nnn 个节点的树,初始时该树的根为 111 号节点,每个节点有一个给定的权值。下面依次进行 mmm 个操作,操作分为如下五种类型:
- 换根:将一个指定的节点设置为树的新根。
- 修改路径权值:给定两个节点,将这两个节点间路径上的所有节点权值(含这两个节点)增加一个给定的值。
- 修改子树权值:给定一个节点,将以该节点为根的子树内的所有节点权值增加一个给定的值。
- 询问路径:询问某条路径上节点的权值和。
- 询问子树:询问某个子树内节点的权值和。
类型分别对应的是1,2,3,4,5;
输出:对于每一个类型为 4 或 5 的操作,输出一行一个整数表示答案.
解析:模板题,就不说其他的了QAQ
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<vector>
#include<queue>
#include<stack>
#include<algorithm>
#include<deque>
#include<cmath>
#include<cctype>
#define LL long long
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
using namespace std;
const int maxn=100000+7;
int n,m;
int a[maxn];
int id=0,root,tot=0;
int fa[maxn],dep[maxn];
int dfn[maxn],top[maxn];
int son[maxn],head[maxn];
int size[maxn],rak[maxn];
struct edge
{
int start,end;
}e[maxn<<1];
int read()
{
char c=getchar();int x=0,f=1;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
void addedge(int u,int v)
{
e[++tot].end=v;
e[tot].start=head[u];
head[u]=tot;
}
struct tre
{
LL tree[maxn<<4],lazy[maxn<<4];
void pushup(int rt)
{
tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}
void pushdown(int l,int r,int rt)
{
if(!lazy[rt]) return ;
int mid=(l+r)>>1;
tree[rt<<1]+=lazy[rt]*(mid-l+1);
tree[rt<<1|1]+=lazy[rt]*(r-mid);
lazy[rt<<1]+=lazy[rt];
lazy[rt<<1|1]+=lazy[rt];
lazy[rt]=0;
}
void buildtree(int l,int r,int rt)
{
if(l==r)
{
tree[rt]=a[dfn[l]];
return ;
}
int mid=(l+r)>>1;
buildtree(lson);
buildtree(rson);
pushup(rt);
}
void update(int l,int r,int rt,int ql,int qr,LL add)
{
int len=r-l+1;
if(ql<=l && qr>=r)
{
lazy[rt]+=add;
tree[rt]+=len*add;
return ;
}
int mid=(l+r)>>1;
pushdown(l,r,rt);
if(ql<=mid) update(lson,ql,qr,add);
if(qr>mid) update(rson,ql,qr,add);
pushup(rt);
}
LL query(int l,int r,int rt,int ql,int qr)
{
if(l>qr||r<ql)return 0;
if(ql<=l && qr>=r)
{
return tree[rt];
}
int mid=(l+r)>>1;
pushdown(l,r,rt);
LL res=0;
if(ql<=mid) res+=query(lson,ql,qr);
if(qr>mid) res+=query(rson,ql,qr);
return res;
}
}T;
//-----orz
void dfs1(int u)
{
size[u]=1;
for(int i=head[u];~i;i=e[i].start)
{
int v=e[i].end;
if(v==fa[u]) continue;
fa[v]=u;
dep[v]=dep[u]+1;
dfs1(v);
size[u]+=size[v];
if(size[v]>size[son[u]]) son[u]=v;
}
}
void dfs2(int u,int now)
{
top[u]=now;
rak[u]=++id;
dfn[id]=u;
if(!son[u]) return ;
dfs2(son[u],now);
for(int i=head[u];~i;i=e[i].start)
{
int v=e[i].end;
if(v!=son[u] && v!=fa[u])
{
dfs2(v,v);
}
}
}
//------QAQ
int check(int x)
{
if(x==root)return -1;
if(!(rak[x]<rak[root] && rak[x]+size[x]-1>=rak[root])) return 0;
int now=root;
while(dep[now]>dep[x])
{
if(fa[top[now]]==x)
return top[now];
now=fa[top[now]];
}
return son[x];//!!!!!!
}
void modify_path(int x,int y,LL add)
{
int fx=top[x],fy=top[y];
while(fx!=fy)
{
if(dep[fx]<dep[fy])
{
swap(fx,fy);
swap(x,y);
}
T.update(1,n,1,rak[fx],rak[x],add);
x=fa[fx];
fx=top[x];
}
if(dep[x]>dep[y]) swap(x,y);
T.update(1,n,1,rak[x],rak[y],add);
}
LL query_path(int x,int y)
{
LL res=0;
int fx=top[x],fy=top[y];
while(fx!=fy)
{
if(dep[fx]<dep[fy])
{
swap(fx,fy);
swap(x,y);
}
res+=T.query(1,n,1,rak[fx],rak[x]);
x=fa[fx];
fx=top[x];
}
if(dep[x]>dep[y]) swap(x,y);
res+=T.query(1,n,1,rak[x],rak[y]);
return res;
}
void modify_tree(int x,LL y)
{
int k=check(x);
switch(k)
{
case -1:T.update(1,n,1,1,n,y);break;
case 0:T.update(1,n,1,rak[x],rak[x]+size[x]-1,y);break;
default:T.update(1,n,1,1,n,y);T.update(1,n,1,rak[k],rak[k]+size[k]-1,-y);break;
}
}
LL query_tree(int x)
{
LL res=0;
int k=check(x);
switch(k)
{
case -1:res=T.tree[1];break;
case 0:res=T.query(1,n,1,rak[x],rak[x]+size[x]-1);break;
default:res=T.tree[1]-T.query(1,n,1,rak[k],rak[k]+size[k]-1);break;
}
return res;
}
//------QAQ
int main()
{
root=1;
n=read();
tot=0;
memset(head,-1,sizeof(head));
for(int i=1;i<=n;i++) a[i]=read();
for(int i=2;i<=n;i++)
{
int x=read();
addedge(x,i);
addedge(i,x);
}
dep[1]=1;
dfs1(1);
dfs2(1,1);
T.buildtree(1,n,1);
m=read();
while(m--)
{
int op=read();
if(op==1)
{
int x=read();
root=x;
}
else if(op==2)
{
int x=read(),y=read(),z=read();
modify_path(x,y,z);
}
else if(op==3)
{
int x=read(),y=read();
modify_tree(x,y);
}
else if(op==4)
{
int x=read(),y=read();
printf("%lld\n",query_path(x,y));
}
else
{
int x=read();
printf("%lld\n",query_tree(x));
}
}
return 0;
}
看吧,其实也没有多少,连三百行都没有到. 摊手/
蓝后下午再写orz,还有两道题