刚入门的同学可以先做做hdu 5692,题解链接:http://www.cnblogs.com/wujiechao/p/6725626.html。
上面是一道典型的dfs时间戳+线段树题。这时候我们想,如果在树上做某条路径上节点的修改,路径节点求和或求最大这样的操作,如果操作的所有结点能在连续的一段区间内,那么我们就能直接用线段树做一个区间的操作解决了,这该多好~这可是log2n的一个操作呢。
但事实是无情的,这样的结构是很难(或者不可能?)实现的。我们就想一个折中的方式,使得平均起来我们要询问的连续区间数量最小。这样路径修改,路径求和这样的操作,我们就能在一个相对小(玄学?)时间内完成。这也是树链刨分的精华所在。也就是所谓的启发式刨分。所谓启发式也就是找一个方案使得这种做法在同类做法里面时间或空间属于较优的。
因此树链刨分是一种通过改变dfs序,使得能让你做树上线段树时,时间较少的一种解决方案。
树链刨分结构
然后就是树链刨分到底是个什么东西呢?
可参考:蒋一瑶神的树链刨分ppt:《树链刨分及其应用》。
我们改变dfs序,让dfs时间戳做出改变。当我们在某个节点p要访问子节点的时候,我们总是先访问子树节点总数最多的一个孩子节点,然后在访问其他的。这样做dfs序的改变,把整棵树分成了一堆重链和轻链(这个后面会解释)。
节点p和他子树节点总数最多的一个孩子节点之间的这条边我们称为重边,那么他的孩子节点和他孩子节点的子树节点总数最多的一个孩子节点之间也会形成一条重边,这样不断往下dfs,这些连在一起的重边就形成了一条重链。你可以发现,每条重链在改变顺序的时间戳上是连续的。
除了子树节点总数最多的那个孩子节点之间的重边外,其他边全是轻边。这个也可以链成一条很长很长的链,这样的链称为轻链,但这样的链没有什么价值,我们查询也不会查询这样的链的区间。因为这些对应的点(如果是边权修改类的那就是两点之间的边)在时间戳上并不都是连续的。
因此我们将树分成了一堆有用的重链,每次操作的区间就是相应部分的重链对应的连续区间。
瑶神的图:
那我们的重链就对应我上文说的查询的连续区间,这样的区间数是相对较小的。设size[p]为p的子树大小,son[p]为p的重链上的儿子(重儿子),也就是子树节点最多的儿子。那么对于除了son[p],其他节点k的size[k]<=size[p]/2。这点显而易见。
假如操作点u,v的lca为p。那么在p分别往u和v的两条路径上,我们每经过一条重链,查找规模就缩小到原来1/2,这样查询的区间数是小于等于log2n。再加上线段树操作是log2n的,那么每次操作一个u和v之间的路径是O((log2n)2)的,效率还是蛮高的。
树链刨分实现
不能免俗的,我们把树链刨分除了线段树部分,也就是求取重链的部分,以及在重链上询问的部分给出。
先是数据结构定义:
1 int fa[N],top[N],num[N],son[N],deep[N],dfstm[N],id[N];
fa为该节点父亲节点,top为该节点的在重链上深度最小的也就是最高的节点,我们可以称之为重链头。num为节点子树节点数量,son为重儿子,deep为深度,id为该节点对应的时间戳上的位置。dfstm[I]则为时间戳上位置为i的树上结点编号。
我们可以先一遍dfs求出fa,num,son,deep。
第二遍的时候依据第一遍的dfs求出top,按照时间戳做法求出id,dfstm。
1 void dfs1(int u,int pre,int dep) 2 { 3 num[u]=1; 4 deep[u]=dep; 5 fa[u]=pre; 6 int p; 7 for(int i=head[u];i!=-1;i=edge[i].next) 8 { 9 p=edge[i].to; 10 if(p!=pre) 11 { 12 dfs1(p,u,dep+1); 13 num[u]+=num[p]; 14 if(son[u]==-1 || num[p]>num[son[u]]) 15 son[u]=p; 16 } 17 } 18 return ; 19 } 20 void dfs2(int u,int tp) 21 { 22 top[u]=tp; 23 id[u]=++cnt; 24 dfstm[cnt]=u; 25 if(son[u]!=-1) 26 dfs2(son[u],tp); 27 int p; 28 for(int i=head[u];i!=-1;i=edge[i].next) 29 { 30 p=edge[i].to; 31 if(p!=fa[u] && p!=son[u]) 32 dfs2(p,p); 33 } 34 return ; 35 }
然后就是询问操作了。当u和v属于不同重链时,每次选择u和v重链头深度较深的一个,假如是u,操作u到重链头对应的区间,然后把u变为重链头的父亲节点。直到u和v属于同一条重链时,操作u和v之间的连续区间,操作over。
给出一个最大值的例子:
1 int querym(int u,int v)//询问树上u到v的最大值 2 { 3 int tpu=top[u],tpv=top[v]; 4 int ans=-0x3f3f3f3f; 5 while(tpu!=tpv) 6 { 7 if(deep[tpu]<deep[tpv]) 8 { 9 swap(tpu,tpv); 10 swap(u,v); 11 } 12 ans=max(ans,querymax(1,id[tpu],id[u])); 13 u=fa[tpu]; 14 tpu=top[u]; 15 } 16 if(deep[u]<deep[v]) swap(u,v); 17 ans=max(ans,querymax(1,id[v],id[u])); 18 return ans; 19 }
其中querymax是询问线段树最大值的一个线段树操作。
至此该实现的都实现了。线段树这个部分你自己写去把23333。
例题
bzoj 1036
1036: [ZJOI2008]树的统计Count
Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 19281 Solved: 7860
[Submit][Status][Discuss]
Description
一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w。我们将以下面的形式来要求你对这棵树完成
一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值 I
II. QSUM u v: 询问从点u到点v的路径上的节点的权值和 注意:从点u到点v的路径上的节点包括u和v本身
Input
输入的第一行为一个整数n,表示节点的个数。接下来n – 1行,每行2个整数a和b,表示节点a和节点b之间有
一条边相连。接下来n行,每行一个整数,第i行的整数wi表示节点i的权值。接下来1行,为一个整数q,表示操作
的总数。接下来q行,每行一个操作,以“CHANGE u t”或者“QMAX u v”或者“QSUM u v”的形式给出。
对于100%的数据,保证1<=n<=30000,0<=q<=200000;中途操作中保证每个节点的权值w在-30000到30000之间。
Output
对于每个“QMAX”或者“QSUM”的操作,每行输出一个整数表示要求输出的结果。
Sample Input
1 2
2 3
4 1
4 2 1 3
12
QMAX 3 4
QMAX 3 3
QMAX 3 2
QMAX 2 3
QSUM 3 4
QSUM 2 1
CHANGE 1 5
QMAX 3 4
CHANGE 3 6
QMAX 3 4
QMAX 2 4
QSUM 3 4
Sample Output
1
2
2
10
6
5
6
5
16
1 #include<bits/stdc++.h> 2 #define clr(x) memset(x,0,sizeof(x)) 3 #define clr_1(x) memset(x,-1,sizeof(x)) 4 #define LL long long 5 #define mod 1000000007 6 using namespace std; 7 const int N=3e4+10; 8 9 struct edg 10 { 11 int next,to; 12 }edge[N*2]; 13 int head[N],ecnt; 14 void addedge(int u,int v) 15 { 16 edge[++ecnt]=(edg){head[u],v}; 17 head[u]=ecnt; 18 return ; 19 } 20 21 int val[N],n,m,q,T,u,v,cnt; 22 int fa[N],top[N],num[N],son[N],deep[N],dfstm[N],id[N]; 23 char s[20]; 24 struct Seg//带有求和和求最大值的线段树 25 { 26 int l,r,maxn,sum; 27 }seg[N<<2]; 28 void init(int i,int l,int r)//初始化线段树 29 { 30 seg[i]=(Seg){l,r}; 31 if(l==r) 32 { 33 seg[i].maxn=seg[i].sum=val[dfstm[l]]; 34 return ; 35 } 36 int mid=(l+r)>>1; 37 init(i<<1,l,mid); 38 init(i<<1|1,mid+1,r); 39 seg[i].maxn=max(seg[i<<1].maxn,seg[i<<1|1].maxn); 40 seg[i].sum=seg[i<<1].sum+seg[i<<1|1].sum; 41 return ; 42 } 43 void update(int i,int pos,int value)//更新pos点的权值带来sum和马鑫改变 44 { 45 if(seg[i].r==pos && seg[i].l==pos) 46 { 47 seg[i].sum=seg[i].maxn=value; 48 return ; 49 } 50 int mid=(seg[i].l+seg[i].r)>>1; 51 if(mid>=pos) 52 { 53 update(i<<1,pos,value); 54 } 55 if(mid<pos) 56 { 57 update(i<<1|1,pos,value); 58 } 59 seg[i].maxn=max(seg[i<<1].maxn,seg[i<<1|1].maxn); 60 seg[i].sum=seg[i<<1].sum+seg[i<<1|1].sum; 61 return ; 62 } 63 int querysum(int i,int l,int r)//询问一个线段树区间[L,R]的和 64 { 65 if(seg[i].l>=l &&seg[i].r<=r) 66 { 67 return seg[i].sum; 68 } 69 int ans=0; 70 int mid=(seg[i].l+seg[i].r)>>1; 71 if(mid>=l) 72 ans+=querysum(i<<1,l,r); 73 if(mid<r) 74 ans+=querysum(i<<1|1,l,r); 75 return ans; 76 } 77 int querymax(int i,int l,int r)//询问一个线段树区间[L,R]的最大值 78 { 79 if(seg[i].l>=l &&seg[i].r<=r) 80 { 81 return seg[i].maxn; 82 } 83 int ans=-0x3f3f3f3f; 84 int mid=(seg[i].l+seg[i].r)>>1; 85 if(mid>=l) 86 ans=max(ans,querymax(i<<1,l,r)); 87 if(mid<r) 88 ans=max(ans,querymax(i<<1|1,l,r)); 89 return ans; 90 } 91 int querys(int u,int v)//询问树上u到v的和 92 { 93 int tpu=top[u],tpv=top[v]; 94 int ans=0; 95 while(tpu!=tpv) 96 { 97 if(deep[tpu]<deep[tpv]) 98 { 99 swap(tpu,tpv); 100 swap(u,v); 101 } 102 ans+=querysum(1,id[tpu],id[u]); 103 u=fa[tpu]; 104 tpu=top[u]; 105 } 106 if(deep[u]<deep[v]) swap(u,v); 107 ans+=querysum(1,id[v],id[u]); 108 return ans; 109 } 110 int querym(int u,int v)//询问树上u到v的最大值 111 { 112 int tpu=top[u],tpv=top[v]; 113 int ans=-0x3f3f3f3f; 114 while(tpu!=tpv) 115 { 116 if(deep[tpu]<deep[tpv]) 117 { 118 swap(tpu,tpv); 119 swap(u,v); 120 } 121 ans=max(ans,querymax(1,id[tpu],id[u])); 122 u=fa[tpu]; 123 tpu=top[u]; 124 } 125 if(deep[u]<deep[v]) swap(u,v); 126 ans=max(ans,querymax(1,id[v],id[u])); 127 return ans; 128 } 129 void inito() 130 { 131 clr_1(head); 132 clr_1(son); 133 ecnt=0; 134 cnt=0; 135 return ; 136 } 137 void dfs1(int u,int pre,int dep) 138 { 139 num[u]=1; 140 deep[u]=dep; 141 fa[u]=pre; 142 int p; 143 for(int i=head[u];i!=-1;i=edge[i].next) 144 { 145 p=edge[i].to; 146 if(p!=pre) 147 { 148 dfs1(p,u,dep+1); 149 num[u]+=num[p]; 150 if(son[u]==-1 || num[p]>num[son[u]]) 151 son[u]=p; 152 } 153 } 154 return ; 155 } 156 void dfs2(int u,int tp) 157 { 158 top[u]=tp; 159 id[u]=++cnt; 160 dfstm[cnt]=u; 161 if(son[u]!=-1) 162 dfs2(son[u],tp); 163 int p; 164 for(int i=head[u];i!=-1;i=edge[i].next) 165 { 166 p=edge[i].to; 167 if(p!=fa[u] && p!=son[u]) 168 dfs2(p,p); 169 } 170 return ; 171 } 172 int main() 173 { 174 scanf("%d",&n); 175 inito(); 176 for(int i=1;i<n;i++) 177 { 178 scanf("%d%d",&u,&v); 179 addedge(u,v); 180 addedge(v,u); 181 } 182 for(int i=1;i<=n;i++) 183 scanf("%d",&val[i]); 184 dfs1(1,1,1); 185 dfs2(1,1); 186 init(1,1,n); 187 scanf("%d",&q); 188 for(int i=1;i<=q;i++) 189 { 190 scanf("%s%d%d",s,&u,&v); 191 if(strcmp(s,"CHANGE")==0) 192 { 193 update(1,id[u],v); 194 } 195 if(strcmp(s,"QSUM")==0) 196 { 197 printf("%d\n",querys(u,v)); 198 } 199 if(strcmp(s,"QMAX")==0) 200 { 201 printf("%d\n",querym(u,v)); 202 } 203 } 204 return 0; 205 }