最近学(复习?)了很多省选算法,先把心得写在这里,将来如果忘了拿来复习吧
一、树链剖分
树链剖分是处理一类在树的一条链上修改、查询最大/最小值/权值和的算法。效率nlog^2n,大概数据在3w到5w左右比较正常吧
树链剖分不支持导致树的形态发生改变的操作,比如插入/删除一条边
对于某一修改x到y路径上的东西操作,正常的模拟做法是先提出x和y的lca,然后一步一步往上走处理x到lca和y到lca的路径
树链剖分的思想只是在这个基础上用线段树优化一步一步往上走的过程
1、无论是暴力还是正解,都要先把无根树转成有根树
所以第一遍dfs1:无根树转成有根树,先求出这些东西:
fa[i][j]:i节点往上走2^j步能走到的点,这一步主要是求lca用的
depth[i]:i节点的深度,还是lca用
son[i]:第i个节点下面的儿子数
2、接下来就要考虑怎样用线段树模拟一步一步往上走的过程
线段树里存的是点权,如果题目给的是边权的话就用这条边指向的儿子节点的点权表示
为什么线段树会快呢?因为如果往上走的时候如果上面的一条链是线段树的一段连续的区间,那么可以直接logn提出我们需要的东西
但是不一定每次往上走的时候走的路径都是线段树的一段连续的区间,所以一开始点权加入线段树的顺序就很重要了
那么怎样才能尽可能保证走的是连续的区间呢
引入轻边与重边:对于一个节点x,它下面可能连出很多边,每条边指向它的一个儿子
假设x的所有儿子中son[y]最大,那么连接(x,y)的边是重边,y是x的重儿子。其他x下面连出的边都是轻边,x的其他儿子也就是轻儿子
找到一个点之后,就直接先把重儿子加入线段树,之后再依次处理其他轻儿子
分析一下这样做的作用:找出儿子中son[y]最大的,相当于子树大小最大的,那么操作经过这些点的概率最大
因此在线段树中使x和y放在一起,那么x和y被一起访问的概率比其他x和x的儿子z被一起访问的概率大
有一个比较不太严格的结论:从x到lca的路径大致要在线段树中找logn次,每次logn,所以平均每次操作log^2n
我的写法可能有点锉
首先还是要一个dfs2:算出每个点加入线段树的顺序
place[i]:i在线段树中的位置
pplace[i]:place[i]的反操作,保存线段树中位置是i的点
belong[i]:线段树中找到i往上走能走到的最上面的一个点。换句话说,在线段树中belong[i]和i之间的边是连续的一段,但是belong[i]的父亲节点和这一段是不连续的
然后一个buildtree建树,叶节点的权就是v[pplace[k]]
对于询问(from,to),在往上走的时候就可以这样写:
while (belong[from]!=belong[to])
{
l=place[belong[from]];:线段数中能走到的最上面的点,belong[from]和from在一个连续的区间
r=place[from];:注意加入线段树的顺序是从上往下的,因此belong[from]在from之前加入,lr千万别搞混
search_in_tree(1,l,r);:各种线段树的查询修改操作不用讲了吧
from=fa[belong[from]][0];:from到belong[from]都做完了,因此from直接跳到belong[from]的父亲节点
}
l=place[to];
r=place[from];
search_in_tree(1,l,r);:别忘了最后from和to在线段树中连续了之后再搞一次
以[ZJOI2008]树的统计为例,以下为核心代码:
1 inline void dfs1(int x,int dep) 2 { 3 if (mrk[x])return; 4 mrk[x]=1;depth[x]=dep;son[x]=1; 5 for(int i=1;i<=15;i++) 6 fa[x][i]=fa[fa[x][i-1]][i-1]; 7 for (int i=head[x];i;i=e[i].next) 8 if (!mrk[e[i].to]) 9 { 10 fa[e[i].to][0]=x; 11 dfs1(e[i].to,dep+1); 12 son[x]+=son[e[i].to]; 13 } 14 } 15 inline void dfs2(int x,int chain) 16 { 17 int k=0,mx=0; 18 place[x]=++tt;belong[x]=chain; 19 pplace[tt]=x; 20 for (int i=head[x];i;i=e[i].next) 21 if (fa[x][0]!=e[i].to) 22 { 23 if (son[e[i].to]>mx) 24 { 25 mx=son[e[i].to]; 26 k=e[i].to; 27 } 28 } 29 if(!k)return; 30 dfs2(k,chain); 31 for(int i=head[x];i;i=e[i].next) 32 if (e[i].to!=k&&e[i].to!=fa[x][0]) 33 dfs2(e[i].to,e[i].to); 34 } 35 inline void update(int k) 36 { 37 tree[k].mx=max(tree[k<<1].mx,tree[k<<1|1].mx); 38 tree[k].tot=tree[k<<1].tot+tree[k<<1|1].tot; 39 } 40 inline void buildtree(int now,int l,int r) 41 { 42 tree[now].l=l;tree[now].r=r; 43 if (l==r) 44 { 45 tree[now].mx=v[pplace[l]]; 46 tree[now].tot=v[pplace[l]]; 47 return; 48 } 49 int mid=(l+r)>>1; 50 buildtree(now<<1,l,mid); 51 buildtree(now<<1|1,mid+1,r); 52 update(now); 53 } 54 inline int LCA(int a,int b) 55 { 56 if (depth[a]<depth[b])swap(a,b); 57 int res=depth[a]-depth[b]; 58 for (int i=0;i<=15;i++) 59 if (res & (1<<i))a=fa[a][i]; 60 for (int i=15;i>=0;i--) 61 if (fa[a][i]!=fa[b][i]) 62 { 63 a=fa[a][i]; 64 b=fa[b][i]; 65 } 66 if(a==b)return a; 67 return fa[a][0]; 68 } 69 inline int ask_in_tree(int now,int x,int y) 70 { 71 int l=tree[now].l,r=tree[now].r; 72 if (l==x&&y==r)return tree[now].mx; 73 int mid=(l+r)>>1; 74 if (y<=mid)return ask_in_tree(now<<1,x,y); 75 else if (x>mid)return ask_in_tree(now<<1|1,x,y); 76 return max(ask_in_tree(now<<1,x,mid),ask_in_tree(now<<1|1,mid+1,y)); 77 } 78 inline int sum_in_tree(int now,int x,int y) 79 { 80 int l=tree[now].l,r=tree[now].r; 81 if (l==x&&y==r)return tree[now].tot; 82 int mid=(l+r)>>1; 83 if (y<=mid)return sum_in_tree(now<<1,x,y); 84 else if (x>mid)return sum_in_tree(now<<1|1,x,y); 85 return sum_in_tree(now<<1,x,mid)+sum_in_tree(now<<1|1,mid+1,y); 86 } 87 inline int ask(int from,int to) 88 { 89 int l,r,mx=-inf; 90 while (belong[from]!=belong[to]) 91 { 92 l=place[belong[from]]; 93 r=place[from]; 94 mx=max(mx,ask_in_tree(1,l,r)); 95 from=fa[belong[from]][0]; 96 97 } 98 l=place[to]; 99 r=place[from]; 100 mx=max(mx,ask_in_tree(1,l,r)); 101 102 return mx; 103 } 104 inline int sum(int from,int to) 105 { 106 int l,r; 107 int s=0; 108 while (belong[from]!=belong[to]) 109 { 110 l=place[belong[from]]; 111 r=place[from]; 112 s+=sum_in_tree(1,l,r); 113 from=fa[belong[from]][0]; 114 } 115 l=place[to]; 116 r=place[from]; 117 s+=sum_in_tree(1,l,r); 118 return s; 119 } 120 inline void change(int now,int x,int dat) 121 { 122 int l=tree[now].l,r=tree[now].r; 123 if (l==r) 124 { 125 tree[now].mx=tree[now].tot=dat; 126 return; 127 } 128 int mid=(l+r)>>1; 129 if (x<=mid)change(now<<1,x,dat); 130 else change(now<<1|1,x,dat); 131 update(now); 132 }