树链剖分学习笔记

(感谢大树的讲解~听了他的讲解以后感觉树链剖分没有想象中的那么难)

树链剖分的定义为,对树上的节点划分成若干个集合,使得每个集合内节点相互之间的连边是一条链。

注意,这里说的边指的是从父亲节点到儿子节点的有向边。例如,一个节点有两个儿子节点,则这三个节点组成的集合不符合上述定义(因为边的方向问题不能算作一条链)。

树链剖分的一种方法是树的轻重链剖分。假设这棵树为有根树。

对每个节点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;
}

  

posted @ 2016-11-05 20:10  清羽晨风  阅读(282)  评论(0编辑  收藏  举报