树链剖分学习笔记
(感谢大树的讲解~听了他的讲解以后感觉树链剖分没有想象中的那么难)
树链剖分的定义为,对树上的节点划分成若干个集合,使得每个集合内节点相互之间的连边是一条链。
注意,这里说的边指的是从父亲节点到儿子节点的有向边。例如,一个节点有两个儿子节点,则这三个节点组成的集合不符合上述定义(因为边的方向问题不能算作一条链)。
树链剖分的一种方法是树的轻重链剖分。假设这棵树为有根树。
对每个节点i,定义size[i]为以该节点为根的子树的节点数目。定义一个节点的重儿子为该节点的所有儿子节点中size最大的一个(如果有多个可任取其一)。该节点的其余儿子节点称为轻儿子。
我们以这样一种方式来进行树链剖分:
1.每个节点的后继为该节点的重儿子,则该后继唯一。将这项操作进行到底,则可得到一条链。
2.对于该节点的轻儿子,以轻儿子本身为根,进行树链剖分,递归进行剖分即可。
则我们可以将树剖分为若干条链。并且从每个点到根节点的路径中经过的链数不会超过(log n),这是因为从每个链的顶端i向上走的时候,这个顶端一定为其父亲节点的轻儿子(否则它的父亲才会是链的顶端,矛盾),所以剩余的子树至少有size[i]个点,初始时size[i]为一,每到顶端向上走一步当前走过的点的数量均最少倍增。因此经过的链数不会超过(log n)。
所以如果我们对于每个链维护一个线段树/数状数组/其他数据结构,则对于每次有关树上两点间路径的查询,均可在O(该数据结构的查询复杂度*log n)内解决。
假设我们需要维护的数据结构为线段树。事实上,我们不需要建立若干棵线段树,我们可以把每个节点映射标号为pos[i],使得每条链中所有节点的pos[i]是连续的,即将每个链连续地映射到一个线段树上。这样,我们就可以只建立一棵线段树,就可以对每条链上的查询计算结果。
然后在谈谈查询操作。我们需要维护这样几个数组
father[i](意义显然……)
top[i]表示点i所在的链中最高点(即下述deep最小的点)的编号
deep:我们可以先把每个链看成一个节点,在这个图中计算每条链的深度。假设点i是链j的一个节点,则deep[i]=j在新图中的深度
这一步类似与倍增LCA的思路。如果我们要查询的路径为(u,v),如果deep[u] deep[v]相等,我们跳过这一步。否则的话不妨设deep[u]>deep[v],我们先让点u不断向上”走“,每次走到father[top[u]],通过查询query(pos[u],pos[top[u]])记录这一段的值,并且合并每次计算的值。直到u和v的deep值相等。
u和v的deep相等时,它们不断同时往上走,走到fahter[top[u]],查询query(pos[u],pos[top[u]]),并且记录合并,直到它们的top节点相同。这时查询query(pos[u],pos[v])并进行最后一次合并即可。
树链剖分的理论部分到此结束。该算法的所有问题都找到了解决方案。
——————————————————————————————————————————————
实现的时候我们大概需要维护这样几个数组
pos top deep father hs(hs[i]表示这一节点的重儿子标号,这一数组也可不维护)
需要进行两次深搜
第一次计算father hs
第二次计算 pos top deep
pos的计算方法:第二次宽搜的时候,对于每个节点,先搜索重儿子,然后打时间戳,即可保证每条链映射到线段树或其他数据结构上都是连续的。
相关题目占坑。
——————————————————————————————————————————————
啊!啊!啊!
noi2015的Day1 T2就是树剖的水水水水水水水题!
线段树只有个区间赋值的操作!!
当时真是好sb啊。。。。如果当时听过大树的讲课多好啊QAQ
这题涉及两种操作:
1.区间赋值从根节点到某固定节点
2.区间赋值某棵子树
用线段树的话,操作一的实现很容易。操作二的实现需要考虑pos的生成方式,其本质为dfs序。只需预处理出每个点的子树的pos最大值,然后区间赋值一次即可。因为其子树的pos标号必定在父亲节点和最大值之间,并且其中不会混入其他子树。
代码很诡异。在本地测没有问题,在COGS和UOJ上测T到死……结果在Luogu莫名就A掉了。原因有待考证。
update20161110:死因:被卡常数
在此记录几个被卡常数以后的做法:
1.快速读入(貌似快了不少)
2.压内存,把没必要的内存清掉,比如线段树3*maxn改成2*maxn(快了不少)
3.函数前加inline(貌似也能快一些)
4.函数尽量少调用,减少调用次数(貌似没快多少……)
5.不要以为cout不慢。。换成printf分分钟AC。。。
AC代码。。QAQ求COGS管理员不要拉黑我,我就是交了十几次还重测了十几次。。
__
20161110再update:
抓大放小,刚才试了一下,貌似scanf和printf就能AC,不要作死用string就好。。
maya我都干了些什么。。而且100000本来n*logn^2就卡时还作死。。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
struct SEG{
int left,right;
int lchild,rchild;
int cover; //区间赋值标记。未赋值定义为-1
void clear(int l=0,int r=0){
left=l;right=r;
lchild=rchild=cover=-1;
}
};
template<class T> inline bool getd(T& x){
int ch=getchar();
bool neg=false;
while(ch!=EOF && ch!='-' && !isdigit(ch)) ch=getchar();
if(ch==EOF) return false;
if(ch=='-'){
neg=true;
ch=getchar();
}
x=ch-'0';
while(isdigit(ch=getchar())) x=x*10+ch-'0';
if(neg) x=-x;
return true;
}
const int maxn=100050;
const int INF=2147483647;
int n,m,tot,t;
SEG tree[2*maxn];
vector<int> G[maxn];
int father[maxn],pos[maxn],top[maxn],heavy[maxn],size1[maxn],low[maxn];
inline int length(int l1,int r1,int l2,int r2){
return max(min(r1,r2)-max(l1,l2)+1,0);
}
inline void dfs_first(int u)
{
size1[u]=1;
int mx=-INF;
for(int i=0;i<G[u].size();i++){
father[G[u][i]]=u;
dfs_first(G[u][i]);
size1[u]+=size1[G[u][i]];
mx=max(mx,size1[G[u][i]]);
}
for(int i=0;i<G[u].size();i++)
if(mx==size1[G[u][i]]) heavy[u]=G[u][i];
}
inline void dfs(int u,int t){
top[u]=t;
pos[u]=tot++;
for(int i=0;i<G[u].size();i++) if(G[u][i]==heavy[u]) dfs(G[u][i],t);
for(int i=0;i<G[u].size();i++) if(G[u][i]!=heavy[u]) dfs(G[u][i],G[u][i]);
}
inline int bulid(int left,int right)
{
int p=t++;
SEG& now=tree[p];
now.clear(left,right);
if(right>left){
int k=(left+right)>>1;
now.lchild=bulid(left,k);
now.rchild=bulid(k+1,right);
}
return p;
}
//cover只有最顶端是对的
inline int query(int root,int l,int r) //区间查询,返回区间和
{
if(root==-1) return 0;
SEG& now=tree[root];
if(now.left>r || now.right<l) return 0;
if(now.cover!=-1) return length(now.left,now.right,l,r)*now.cover;
return query(now.lchild,l,r)+query(now.rchild,l,r);
}
inline void change(int root,int l,int r,int t,int road) //区间覆盖
{
if(root==-1) return;
SEG& now=tree[root];
if(road!=-1) now.cover=road;
if(now.left>=l && now.right<=r){
now.cover=t;
return;
}
if(now.right<l || now.left>r) return;
change(now.lchild,l,r,t,now.cover);
change(now.rchild,l,r,t,now.cover);
now.cover=-1;
}
inline int find_max_pos(int u)
{
if(low[u]!=-1) return low[u];
int ans=pos[u];
int t=-1;
for(int i=G[u].size()-1;i>=0;i--) if(G[u][i]!=heavy[u] || i==0){
t=i;
ans=find_max_pos(G[u][i]);
break;
}
for(int i=0;i<G[u].size();i++) if(i!=t) find_max_pos(G[u][i]);
low[u]=ans;
return ans;
}
inline void query_install(int t)
{
int ans=0;
while(t!=-1){
ans+=(pos[t]-pos[top[t]]+1)-query(0,pos[top[t]],pos[t]);
change(0,pos[top[t]],pos[t],1,-1);
t=father[top[t]];
}
printf("%d\n",ans);
}
inline void query_unstall(int t)
{
// int mxpos=find_max_pos(t);
printf("%d\n",query(0,pos[t],low[t]));
change(0,pos[t],low[t],0,-1);
}
inline void init()
{
getd(n);
for(int i=1;i<n;i++){
int t;
getd(t);
G[t].push_back(i); //注意:本题为单向边,从父亲节点指向儿子节点。反向的边存在father里面
}
tot=0;
t=0;
father[0]=-1;
dfs_first(0);
dfs(0,0);
bulid(0,tot-1); //建树,预处理
// for(int i=0;i<n;i++) change(0,i,i,0,-1);
getd(m);
}
inline void work()
{
memset(low,-1,sizeof(low));
find_max_pos(0);
for(int i=0;i<m;i++){
char s[10];
int t;
scanf("%s%d",s,&t);
if(s[0]=='i') query_install(t);//安装:安装一条路径
else if(s[0]=='u') query_unstall(t);//卸载:删除一个子树
}
}
int main()
{
// freopen("manager.in","r",stdin);
// freopen("manager.out","w",stdout);
#ifdef LOCAL
// freopen("input.txt","r",stdin);
#endif
init();
work();
fclose(stdin);
fclose(stdout);
return 0;
}

浙公网安备 33010602011771号