树链剖分,顾名思义为将链剖开分成多条。
当我们想要修改树上一条路的值或求值时,我们暴力只能用一个个修改,这是非常慢的。这时,我们就要想一个办法,数据结构?但是数据结构我们都需要连续修改,可是树上路径的编号是不连续的。于是我们想了一个办法。
我们先定义
fa[x]为x的父亲
dep[x]为x的深度
size[x]为以x为根的子树的节点个数
几个名词:
1.重边(一个节点的儿子中size最大的点与这个点之间的边)
2.重儿子 x的重儿子记为son[x](一个节点重边连接的儿子)
3.轻边(一个节点除重儿子外与其他儿子链接的边)
4.轻儿子 (x除重儿子外的儿子)
5.重链(由重边连接而成的链)
6.轻链(由轻边连接而成的链)
然后我们再定义
top[x]为x所在的重链中深度最小的节点(若x不为fa[x]的重儿子则top[x]=x,即自己一个点形成一条重链)
于是我们发现重链上的点标号都是连续的。
并且一颗子树中的编号也是连续的,且子树的根的编号是最小的。

我们举个例子,在这棵树中,son[1]=2(因为size[2]比size[3]和size[4]大)。
1,2,6,9,11构成一条重链;3,7,10构成一条重链。
top[1]=top[2]=top[6]=top[9]=top[11]=1
top[3]=top[7]=top[10]=3
top[5]=5 top[4]=4 top[8]=8
然后又这样一条性质,从根到任意一节点的路径上轻链,重链的个数都不大于logN(N为节点个数)。
现在我们将一条重链上连续标号,建立新的标号(这是为了可以在数据结构中连续修改)。
我们dfs遍历,优先走重儿子,则上面那个图的新标号tid为:
tid[1]=1 tid[2]=2 tid[6]=3 tid[9]=4 tid[11]=5
tid[5]=6
tid[3]=7 tid[7]=8 tid[10]=9
tid[8]=10
tid[4]=11
我们有了新的编号,所有的准备工作都已经完成了。
令操作为修改(求值)u到v的路径。
1. 若top[u]=top[v] 则我们可以直接在线段树中修改(求值)tid[u]~tid[v](假定tid[u]<=tid[v])的区间。
2. 若top[u]!=top[v] 不妨设dep[top[u]]>dep[top[v]]则我们可以使u跳到fa[top[u]]的位子,这样我们可以使u离v更近一些,使得u和v尽快跳到一条重链上。并且由于重链的编号是连续的,所以我们只需要修改(求值)tid[top[u]]~tid[u]的区间即可。
我们举个例子:
上图中从10~11,即u=10,v=11,因为dep[top[v]]<dep[top[u]],那么我们先使u跳到fa[top[10]]=1,然后u和v就在一条重链上了(这里正好重合了),那么我们就可以直接修改了。
BZOJ1036[ZJOI2008]树的统计Count
这题是树链剖分裸题,下面给出此题代码以供参考和理解。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #define N 30500 using namespace std; int n,tot,lable,Next[N*2],head[N*2],tree[N*2],a[N],m,fa[N],dep[N],son[N],size[N],tid[N],number[N]; int top[N],Max[N*8],sum[N*8]; bool visit[N]; void add(int x,int y) { tot++; Next[tot]=head[x]; head[x]=tot; tree[tot]=y; } void dfs(int x,int depth,int father) { visit[x]=true;fa[x]=father;dep[x]=depth;son[x]=0;size[x]=1; int maxsize=0; for (int i=head[x];i;i=Next[i]) if (!visit[tree[i]]) { dfs(tree[i],depth+1,x); size[x]+=size[tree[i]]; if (size[tree[i]]>maxsize) { maxsize=size[tree[i]]; son[x]=tree[i]; } } } void dfs1(int x,int ancestor) { visit[x]=true;tid[x]=++lable;number[lable]=x;top[x]=ancestor; if (son[x]!=0) dfs1(son[x],ancestor); for (int i=head[x];i;i=Next[i]) if (!visit[tree[i]]) dfs1(tree[i],tree[i]); } void up(int x) { Max[x]=max(Max[x*2],Max[x*2+1]); sum[x]=sum[x*2]+sum[x*2+1]; } void build(int l,int r,int id) { Max[id]=-1<<29;sum[id]=0; if (l==r) { Max[id]=sum[id]=a[number[l]]; return; } int mid=(l+r)/2; build(l,mid,id*2); build(mid+1,r,id*2+1); up(id); } void change(int x,int l,int r,int id,int d) { if (l>x||r<x) return; if (l==r&&l==x) { Max[id]=sum[id]=d; return; } int mid=(l+r)/2; change(x,l,mid,id*2,d); change(x,mid+1,r,id*2+1,d); up(id); } int query(int x,int y,int id,int l,int r,int q) { if (l>y||r<x) { if (q==0) return -1<<29;else return 0; } if (x<=l&&r<=y) { if (q==0) return Max[id];else return sum[id]; } int mid=(l+r)/2; if (q==0) return max(query(x,y,id*2,l,mid,q),query(x,y,id*2+1,mid+1,r,q)); else return query(x,y,id*2,l,mid,q)+query(x,y,id*2+1,mid+1,r,q); up(id); } int Query(int x,int y,int q) { int ans; if (q==0) ans=-1<<29;else ans=0; while (top[x]!=top[y]) { if (dep[top[x]]<dep[top[y]]) swap(x,y); if (q==0) ans=max(ans,query(tid[top[x]],tid[x],1,1,n,0)); else ans+=query(tid[top[x]],tid[x],1,1,n,1); x=fa[top[x]]; } if (dep[x]>dep[y]) swap(x,y); if (q==0) ans=max(ans,query(tid[x],tid[y],1,1,n,0)); else ans+=query(tid[x],tid[y],1,1,n,1); return ans; } int main() { scanf("%d",&n); tot=lable=0; for (int i=1;i<n;i++) { int x,y; scanf("%d%d",&x,&y); add(x,y);add(y,x); } for (int i=1;i<=n;i++) scanf("%d",&a[i]); scanf("%d",&m); for (int i=1;i<=n;i++) visit[i]=false; dfs(1,1,0); for (int i=1;i<=n;i++) visit[i]=false; dfs1(1,1); build(1,n,1); char s[100]; for (int i=1;i<=m;i++) { int x,y; scanf("%s%d%d",s,&x,&y); if (s[0]=='C') change(tid[x],1,n,1,y); if (s[1]=='M') printf("%d\n",Query(x,y,0)); if (s[1]=='S') printf("%d\n",Query(x,y,1)); } return 0; } |
有一点细节没讲清楚,线段树上的操作还要自己YY下,总的来说挺清楚的,已A了模板题
2460 树的统计 测试通过 Accepted 100 1192ms 10 MB C++ 5478B 2017/07/02 10:58:18