定义:
将整棵树剖分为若干条链,使它组合成线性结构,然后用其他的数据结构维护信息。就是一种技巧,为了方便处理问题。
形式:重链剖分,长链剖分等。此处讲述重链剖分。
重链剖分:
可以将树上的任意一条路径划分成不超过 logn 条连续的链,每条链上的点深度互不相同(即是自底向上的一条链,链上所有点的 LCA 为链的一个端点)。还能保证划分出的每条链上的节点 DFS 序连续。
树上每个节点都属于且仅属于一条重链 。
性质:
(1)重链开头的结点不一定是重子节点;
(2)所有的重链将整棵树完全剖分 ;
(3)在剖分时<mark>优先遍历重儿子</mark>,最后每条重链的 DFS 序就会是连续的;
(4)一棵子树内各点在 dfn 数组中的编号是连续的;
(5)当向下经过一条轻边时,所在子树的大小至少会除以2;
(6)对于树上的任意一条路径,把它拆分成从 lca 分别向两边往下走,分别最多走 logn 次。因此,<mark>树上的每条路径都可以被拆分成不超过 logn 条重链</mark>。
实现:
相关数组定义:
fa[x]: 表示节点 x 在树上的父亲;
depth[x]: 表示节点 x 在树上的深度;
size[x]: 表示以节点 x 为根的子树的节点个数;
son[x]: 表示节点 x 的重儿子 ;
top[x]: 表示节点 x 所在重链的顶部节点(深度最小);
dfn[x]: 表示节点 x 的 DFS 序编号,即生成 dfs 序时,节点 x 在 rnk 中出现的编号;
rnk[x]:表示 DFS 遍历各点时所对应的节点编号,与 dfn[x] 表示的意义相反。
具体过程可以分为两遍 dfs:
第一遍:求出 fa[x],depth[x],size[x],son[x] 的值。
大致过程和 LCA 中 dfs 过程大致相同。
void dfs1(int v,int p,int d)//当前点,父亲节点,深度
{
fa[v]=p;//存父亲
depth[v]=d;//记深度
size[v]=1;//子树大小
for(int i=0;i<pic[v].size();i++)
{
int t=pic[v][i];
if(t!=p)
{
dfs1(t,v,d+1);
size[v]+=size[t];//回溯过程求子树的大小
if(son[v]==-1||size[t]>size[son[v]])//更新重儿子
son[v]=t;
}
}
}
第二遍 dfs :求出 top,dfn,rnk 数组。
此处的 dfs 序生成 rnk 数组和基于 rmq 的 lca 中的 dfs 序有所不同,每个点只在 rnk 数组中<mark>出现一次</mark>。
经过此次的 dfs ,原来的树就变成一维的有序序列。而且,同一条链上的点在序列中连续,深度小的点位置靠前。但是对于一条边相连的两个点,如果不在同一条链上,在序列中的位置就不在一起。整体的效果就相当于把整棵树,劈成不同的链,然后按照一定的顺序,把各条链摆放在 rnk 数组中。
void dfs2(int v,int p,int tp,int &cnt)//当前点,当前点的父亲节点,链顶点,rnk中编号
{
top[v]=tp;//v所在的链的端点
dfn[v]=cnt;//在rnk数组中的编号
rnk[cnt++]=v;
if(son[v]==-1)//链的末尾
return;
dfs2(son[v],v,tp,cnt);//优先遍历重儿子,保证同一条重链上的点的dfs序连续
for(int i=0;i<pic[v].size();i++)
{
int t=pic[v][i];
if(t!=son[v]&&t!=p)
dfs2(t,v,t,cnt);//链的起始
}
}
应用:
1.求 lca:
不断向上跳重链,当跳到同一条重链上时,深度较小的结点即为 LCA。<mark>向上跳重链时需要先跳所在重链顶端深度较大的那个</mark>。
常数更小。
int lca(int u, int v)
{
while (top[u]!=top[v])
{
if(depth[top[u]]>depth[top[v]])
u=fa[top[u]];
else
v=fa[top[v]];
}
return depth[u]<=depth[v]?u:v;
}
2.子树维护:
比如将以 x 为根的子树的所有结点的权值增加。在 DFS 搜索的时候,<mark>子树中的结点的 DFS 序是连续的</mark>。每一个结点记录 bottom 表示所在子树连续区间末端的结点。这样就把子树信息转化为连续的一段区间信息。
3.路径上维护:
用树链剖分求树上两点路径权值和,伪代码如下:
用线段树或树状数组维护。
例题:
1.Query on a tree SPOJ - QTREE
问题:
1.改变某一条边权
2.求两点之间的最大边权
用树链剖分预处理,用线段树维护边权。求两点之间的最大边权时,在求 lca 跳链的过程中用线段树求。
#include <bits/stdc++.h>
using namespace std;
const int N=1e4+5;
typedef long long ll;
struct edge
{
int from,to;
ll w;
};
ll tree[N<<2];
int size[N],son[N],depth[N],fa[N],dfn[N],rnk[N],top[N];
vector<int>pic[N];
vector<edge>eg;
void dfs1(int v,int p,int d)
{
fa[v]=p;
depth[v]=d;
size[v]=1;
for(int i=0;i<pic[v].size();i++)
{
int t=pic[v][i];
if(t!=p)
{
dfs1(t,v,d+1);
size[v]+=size[t];
if(son[v]==-1||size[t]>size[son[v]])
son[v]=t;
}
}
}
void dfs2(int v,int p,int tp,int &cnt)
{
top[v]=tp;
dfn[v]=cnt;
rnk[cnt++]=v;
if(son[v]==-1)//链的末尾
return;
dfs2(son[v],v,tp,cnt);//优先遍历重儿子,保证同一条重链上的点的dfs序连续
for(int i=0;i<pic[v].size();i++)
{
int t=pic[v][i];
if(t!=son[v]&&t!=p)
dfs2(t,v,t,cnt);//链的起始
}
}
void build(int l,int r,int rt)
{
if(l==r)
{
tree[rt]=0;
return;
}
int mid=(l+r)>>1;
build(l,mid,rt<<1);
build(mid+1,r,rt<<1|1);
tree[rt]=max(tree[rt<<1],tree[rt<<1|1]);
}
void update(int l,int r,int c,int rt,ll num)
{
if(l==r)
{
tree[rt]=num;
return;
}
int mid=(l+r)>>1;
if(c<=mid)
update(l,mid,c,rt<<1,num);
else if(c>mid)
update(mid+1,r,c,rt<<1|1,num);
tree[rt]=max(tree[rt<<1],tree[rt<<1|1]);
}
ll query(int L,int R,int l,int r,int rt)
{
if(L<=l&&r<=R)
return tree[rt];
int mid=(l+r)>>1;
ll ans=0;
if(L<=mid)
ans=max(ans,query(L,R,l,mid,rt<<1));
if(R>mid)
ans=max(ans,query(L,R,mid+1,r,rt<<1|1));
return ans;
}
ll lca_ans(int u,int v,int n)
{
ll ans=0;
while(top[u]!=top[v])//在求lca的过程中求最大边权
{
if(depth[top[u]]<depth[top[v]])
swap(u,v);
ans=max(ans,query(dfn[top[u]],dfn[u],1,n,1));
u=fa[top[u]];//换链
}
if(u!=v)
{
if(depth[u]<depth[v])
ans=max(ans,query(dfn[u]+1,dfn[v],1,n,1));
else
ans=max(ans,query(dfn[v]+1,dfn[u],1,n,1));
}
return ans;
}
int main()
{
int t,n;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
int u,v;
ll w;
for(int i=1;i<=n;i++)
pic[i].clear();
eg.clear();
memset(son,-1,sizeof(son));
for(int i=1;i<=n;i++)
top[i]=i;
for(int i=1;i<n;i++)
{
scanf("%d%d%lld",&u,&v,&w);
pic[u].push_back(v);
pic[v].push_back(u);
eg.push_back({u,v,w});
}
dfs1(1,1,0);
int cnt=1;
dfs2(1,-1,1,cnt);
build(1,n,1);
for(int i=0;i<n-1;i++)
{
edge e=eg[i];
u=max(dfn[e.from],dfn[e.to]);
update(1,n,u,1,e.w);
}
char ss[10]={0};
while(1)
{
scanf("%s",ss);
if(strcmp(ss,"DONE")==0)
break;
if(strcmp(ss,"CHANGE")==0)
{
scanf("%d%lld",&u,&w);
edge e=eg[u-1];
u=max(dfn[e.from],dfn[e.to]);
update(1,n,u,1,w);
}
else if(strcmp(ss,"QUERY")==0)
{
scanf("%d%d",&u,&v);
printf("%lld\n",lca_ans(u,v,n));
}
}
if(t)
puts("");
}
return 0;
}
/* 2 9 1 2 1 1 3 1 1 4 2 2 6 1 2 7 1 3 5 2 5 9 1 4 8 2 */
Aragorn’s Story HDU - 3966【模板】
#include <bits/stdc++.h>
using namespace std;
const int N=5e4+5000;
int son[N],size[N],fa[N],top[N],depth[N],dfn[N],rnk[N];
int tree[N<<2],a[N],lazy[N<<2];
vector<int>pic[N];
void dfs1(int v,int p,int d)
{
fa[v]=p;
depth[v]=d;
size[v]=1;
for(int i=0;i<pic[v].size();i++)
{
int t=pic[v][i];
if(t!=p)
{
dfs1(t,v,d+1);
size[v]+=size[t];
if(son[v]==-1||size[t]>size[son[v]])
son[v]=t;
}
}
}
void dfs2(int v,int p,int tp,int &cnt)
{
top[v]=tp;
rnk[cnt]=v;
dfn[v]=cnt++;
if(son[v]==-1)
return;
dfs2(son[v],v,tp,cnt);
for(int i=0;i<pic[v].size();i++)
{
int t=pic[v][i];
if(son[v]!=t&&p!=t)
dfs2(t,v,t,cnt);
}
}
void build(int l,int r,int rt)
{
if(l==r)
{
lazy[rt]=0;
tree[rt]=a[rnk[l]];
return;
}
int mid=(l+r)>>1;
build(l,mid,rt<<1);
build(mid+1,r,rt<<1|1);
tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}
void pushdown(int rt,int ln,int rn)
{
if(lazy[rt])
{
lazy[rt<<1]+=lazy[rt];
tree[rt<<1]+=lazy[rt]*ln;
lazy[rt<<1|1]+=lazy[rt];
tree[rt<<1|1]+=lazy[rt]*rn;
lazy[rt]=0;
}
}
void update(int l,int r,int L,int R,int num,int rt)
{
if(L<=l&&r<=R)
{
tree[rt]+=num*(r-l+1);
lazy[rt]+=num;
return;
}
int mid=(l+r)>>1;
pushdown(rt,mid-l+1,r-mid);
if(L<=mid)
update(l,mid,L,R,num,rt<<1);
if(R>mid)
update(mid+1,r,L,R,num,rt<<1|1);
tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}
int query(int l,int r,int L,int R,int rt)
{
if(L<=l&&r<=R)
return tree[rt];
int mid=(l+r)>>1;
pushdown(rt,mid-l+1,r-mid);
int ans=0;
if(L<=mid)
ans+=query(l,mid,L,R,rt<<1);
if(R>mid)
ans+=query(mid+1,r,L,R,rt<<1|1);
return ans;
}
void path_update(int u,int v,int w,int n)
{
while(top[u]!=top[v])
{
if(depth[top[u]]<depth[top[v]])
swap(u,v);
update(1,n,dfn[top[u]],dfn[u],w,1);
u=fa[top[u]];
}
if(depth[u]>depth[v])
swap(u,v);
update(1,n,dfn[u],dfn[v],w,1);
}
int main()
{
int n,m,p;
while(scanf("%d%d%d",&n,&m,&p)!=EOF)
{
int u,v,w,cnt=1;
char ss[8]={0};
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
pic[i].clear();
memset(son,-1,sizeof(son));
memset(lazy,0,sizeof(lazy));
for(int i=1;i<=m;i++)
{
scanf("%d%d",&u,&v);
pic[u].push_back(v);
pic[v].push_back(u);
}
dfs1(1,1,0);
dfs2(1,1,1,cnt);
build(1,n,1);
for(int i=1;i<=p;i++)
{
scanf("%s",ss);
//用一个字符+getchar:re数组访问越界
//getchar();
//char cc;
//scanf("%c",&cc);
if(ss[0]=='Q')
{
scanf("%d",&u);
printf("%d\n",query(1,n,dfn[u],dfn[u],1));
}
else if(ss[0]=='I')
{
scanf("%d%d%d",&u,&v,&w);
path_update(u,v,w,n);
}
else if(ss[0]=='D')
{
scanf("%d%d%d",&u,&v,&w);
path_update(u,v,-w,n);
}
}
}
return 0;
}
/* 3 2 5 1 2 3 2 1 2 3 I 1 3 5 Q 2 D 1 2 2 Q 1 Q 3 */
Tree HDU - 6547
1.将一条链上的所有节点的值开根号向下取整;
2.求一条链上值的和;
由数据范围,控制开根号的次数有限,所以可以对每个点就行更新。
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
typedef long long ll;
ll tree[N<<2];
ll a[N];
vector<int>pic[N];
int n;
int son[N],size[N],depth[N],fa[N],dfn[N],top[N],rnk[N];
void dfs1(int v,int p,int d)
{
fa[v]=p;
depth[v]=d;
size[v]=1;
for(int i=0;i<pic[v].size();i++)
{
int u=pic[v][i];
if(u!=p)
{
dfs1(u,v,d+1);
size[v]+=size[u];
if(son[v]==-1||size[son[v]]<size[u])
son[v]=u;
}
}
}
void dfs2(int v,int p,int tp,int &cnt)
{
top[v]=tp;
dfn[v]=cnt;
rnk[cnt++]=v;
if(son[v]==-1)
return;
dfs2(son[v],v,tp,cnt);
for(int i=0;i<pic[v].size();i++)
{
int u=pic[v][i];
if(u!=p&&u!=son[v])
dfs2(u,v,u,cnt);
}
}
void build(int l,int r,int rt)
{
if(l==r)
{
tree[rt]=a[rnk[l]];
return;
}
int mid=(l+r)>>1;
build(l,mid,rt<<1);
build(mid+1,r,rt<<1|1);
tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}
void update(int l,int r,int L,int R,int rt)
{
if(tree[rt]<=r-l+1)
return;
if(l==r)
{
tree[rt]=(ll)sqrt(1.0*tree[rt]);
return;
}
int mid=(l+r)>>1;
if(L<=mid)
update(l,mid,L,R,rt<<1);
if(R>mid)
update(mid+1,r,L,R,rt<<1|1);
tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}
ll query(int l,int r,int L,int R,int rt)
{
if(L<=l&&r<=R)
return tree[rt];
int mid=(l+r)>>1;
ll ans=0;
if(L<=mid)
ans+=query(l,mid,L,R,rt<<1);
if(R>mid)
ans+=query(mid+1,r,L,R,rt<<1|1);
return ans;
}
void path_update(int u,int v)
{
while(top[u]!=top[v])
{
if(depth[top[u]]<depth[top[v]])
swap(u,v);
update(1,n,dfn[top[u]],dfn[u],1);
u=fa[top[u]];
}
if(depth[u]<depth[v])
swap(u,v);
update(1,n,dfn[v],dfn[u],1);
}
ll path_query(int u,int v)
{
ll ans=0;
while(top[u]!=top[v])
{
if(depth[top[u]]<depth[top[v]])
swap(u,v);
ans+=query(1,n,dfn[top[u]],dfn[u],1);
u=fa[top[u]];//cout<<"ans="<<ans<<endl;
}
if(depth[u]<depth[v])
swap(u,v);
ans+=query(1,n,dfn[v],dfn[u],1);
return ans;
}
int main()
{
int q,op,u,v;
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
memset(son,-1,sizeof(son));
for(int i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
pic[u].push_back(v);
pic[v].push_back(u);
}
int cnt=1;
dfs1(1,1,0);
dfs2(1,1,1,cnt);
build(1,n,1);
while(q--)
{
scanf("%d%d%d",&op,&u,&v);
if(op==0)
path_update(u,v);
else if(op==1)
printf("%lld\n",path_query(u,v));
}
return 0;
}
染色 HYSBZ - 2243
题意:
给定一棵有 n 个节点的无根树和m个操作,操作有 2 类:
- 将节点 a 到节点 b 路径上所有点都染成颜色 c;
- 询问节点 a 到节点 b 路径上的颜色段数量(连续相同颜色被认为是同一段);
如“112221”由3段组成:“11”、“222”和“1”。
请你写一个程序依次完成这 m 个操作。
分析:
树剖之后用线段树维护区间颜色段数,区间查询和区间修改。线段树结点中维护的有:段数,左端点颜色,右端点颜色和懒惰标记。
- 当区间合并时,若左孩子的右端点颜色和右孩子的左端点颜色相同,那么段数要 −1;
- 区间修改时注意维护左右端点的颜色;
- 查询时若左右子区间连接处的颜色相同,段数 −1;
- 跳链时,求出当前链的颜色段数后,要比较该段链的顶点和其父亲的颜色是否相同,如果是,则 −1,直到两点处于同一条链为止。
错误:本来跳链时应该优先跳所在链顶点深度大的,即:
if(depth[top[u]]>depth[top[v]]) swap(u,v);
但是写成了
if(depth[u]>depth[v]) swap(u,v);
WA 了好几次。
代码:
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
const int N=1e5+5;
struct node
{
int sum,lc,rc;
}tree[N<<2];
int lazy[N<<2],top[N],dfn[N],fa[N],depth[N],sz[N],son[N],c[N],rnk[N];
vector<int>pic[N];
int n;
void dfs1(int v,int p,int d)
{
sz[v]=1;
son[v]=-1;
depth[v]=d;
fa[v]=p;
for(int i=0;i<pic[v].size();i++)
{
int u=pic[v][i];
if(u!=p)
{
dfs1(u,v,d+1);
sz[v]+=sz[u];
if(son[v]==-1||sz[u]>sz[son[v]])
son[v]=u;
}
}
}
void dfs2(int v,int p,int tp,int &cnt)
{
top[v]=tp;
dfn[v]=++cnt;
rnk[cnt]=v;
if(son[v]==-1)
return;
dfs2(son[v],v,tp,cnt);
for(int i=0;i<pic[v].size();i++)
{
int u=pic[v][i];
if(u!=p&&u!=son[v])
dfs2(u,v,u,cnt);
}
}
void pushup(int rt)
{
tree[rt].sum=tree[rt<<1].sum+tree[rt<<1|1].sum;
if(tree[rt<<1].rc==tree[rt<<1|1].lc)
tree[rt].sum--;
tree[rt].lc=tree[rt<<1].lc;
tree[rt].rc=tree[rt<<1|1].rc;
}
void pushdown(int rt)
{
if(lazy[rt]>-1)
{
tree[rt<<1].sum=tree[rt].sum;
tree[rt<<1].lc=lazy[rt];
tree[rt<<1].rc=lazy[rt];
lazy[rt<<1]=lazy[rt];
tree[rt<<1|1].sum=tree[rt].sum;
tree[rt<<1|1].lc=lazy[rt];
tree[rt<<1|1].rc=lazy[rt];
lazy[rt<<1|1]=lazy[rt];
lazy[rt]=-1;
}
}
void build(int l,int r,int rt)
{
lazy[rt]=-1;
if(l==r)
{
tree[rt].sum=1;
tree[rt].lc=c[rnk[l]];
tree[rt].rc=c[rnk[l]];
return;
}
int mid=(l+r)>>1;
build(l,mid,rt<<1);
build(mid+1,r,rt<<1|1);
pushup(rt);
}
void update(int l,int r,int L,int R,int val,int rt)
{
if(L<=l&&r<=R)
{
tree[rt].sum=1;
tree[rt].lc=val;
tree[rt].rc=val;
lazy[rt]=val;
return;
}
pushdown(rt);
int mid=(l+r)>>1;
if(L<=mid)
update(l,mid,L,R,val,rt<<1);
if(R>mid)
update(mid+1,r,L,R,val,rt<<1|1);
pushup(rt);
}
node query(int l,int r,int L,int R,int rt)
{
if(L<=l&&r<=R)
return tree[rt];
node ans,t1,t2;
int mid=(l+r)>>1;
pushdown(rt);
if(R<=mid)
return query(l,mid,L,R,rt<<1);
else if(L>mid)
return query(mid+1,r,L,R,rt<<1|1);
else
{
t1=query(l,mid,L,R,rt<<1);
t2=query(mid+1,r,L,R,rt<<1|1);
ans.sum=t1.sum+t2.sum;
if(t1.rc==t2.lc)
ans.sum--;
ans.lc=t1.lc;
ans.rc=t2.rc;
return ans;
}
}
void change(int u,int v,int c)
{
while(top[u]!=top[v])
{
if(depth[top[u]]>depth[top[v]])
swap(u,v);
update(1,n,dfn[top[v]],dfn[v],c,1);
v=fa[top[v]];
}
if(depth[u]>depth[v])
swap(u,v);
update(1,n,dfn[u],dfn[v],c,1);
}
int ask(int u,int v)
{
node t;
int ans=0;
while(top[u]!=top[v])
{
if(depth[top[u]]>depth[top[v]])
swap(u,v);
t=query(1,n,dfn[top[v]],dfn[v],1);
ans+=t.sum;
if(query(1,n,dfn[fa[top[v]]],dfn[fa[top[v]]],1).rc==t.lc)
ans--;
v=fa[top[v]];
}
if(depth[u]>depth[v])
swap(u,v);
t=query(1,n,dfn[u],dfn[v],1);
ans+=t.sum;
return ans;
}
int main()
{
int m,u,v,cc;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&c[i]);
for(int i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
pic[u].pb(v);
pic[v].pb(u);
}
char op[5];
int cnt=0;
dfs1(1,1,0);
dfs2(1,0,1,cnt);
build(1,n,1);
for(int i=1;i<=m;i++)
{
scanf("%s",op);
if(op[0]=='C')
{
scanf("%d%d%d",&u,&v,&cc);
change(u,v,cc);
}
else if(op[0]=='Q')
{
scanf("%d%d",&u,&v);
printf("%d\n",ask(u,v));
}
}
return 0;
}