DFS序——树链剖分前驱知识

目录

定义:dfs序:每个节点在dfs深度优先遍历中的进出栈的时间序列。 

 性质:dfs序可以把一棵树区间化,即可以求出每个节点的管辖区间。

对于一棵树的dfs序而言,同一棵子树所对应的一定是dfs序中连续的一段。

dfs序的七个基本问题:


定义:dfs序:每个节点在dfs深度优先遍历中的进出栈的时间序列。 

定义两个数组,in[x],out[x]。dfs从根结点开始,每个结点分别记录两个信息:in[x],out[x],in[x]为dfs进入结点x时的时间戳,out[x]为dfs离开结点x时的时间戳。

dfs序的基本代码:

void dfs(int x,int pre,int d){//L,R表示一个子树的范围  
    L[x]=++tot;  
    dep[x]=d;  
    for(int i=0;i<e[x].size();i++){  
        int y=e[x][i];  
        if(y==pre)continue;  
        dfs(y,x,d+1);  
    }  
    R[x]=tot;  
}

 

这里写图片描述 
dfs序就是: 
这里写图片描述

 性质:dfs序可以把一棵树区间化,即可以求出每个节点的管辖区间。

对于一棵树的dfs序而言,同一棵子树所对应的一定是dfs序中连续的一段。

 

这里写图片描述

 

如下图,DFS之后,那么树的每个节点就具有了区间的性质。 
这里写图片描述

dfs序的七个基本问题:

ps:deep[x]为x的深度,l[x]为dfs序中x的位置,r[x]为dfs序中x子树的结束位置

1.点修改,子树和查询

  在dfs序中,子树处于一个连续区间中。所以这题可以转化为:点修改,区间查询。用树状数组或线段树即可。

例:poj3321 Apple Tree

链接:邻接表数组实现详解

//dfs序+树状数组
#include <iostream>
#include <stdio.h>
#include <math.h>
#include <string.h>
using namespace std;
const int N = 500005;
struct Edge{ //邻接表——数组实现 (链式前向星)
    int to,next;
}edge[N];

int head[N],tot,d[N];
int in[N],out[N];  //in[i]:dfs第一次进入顶点i的时间戳,in[i]为dfs离开该节点的时间戳 
bool have[N];
int cnt;

void init(){
    tot = 0;
    cnt = 0;
    memset(head,-1,sizeof(head));
    memset(d,0,sizeof(d));
}

void addEdge(int u,int v,int &k){
    edge[k].to = v;
	edge[k].next = head[u];  //edge[k].next存储的是编号为i的边的(同样以u为起始点的)前一条边的编号 
	head[u] = k++;  //最后一条以u为起始点的边的编号 
}

void dfs(int u){  //dfs序 
    in[u] = ++cnt;
    for(int k=head[u];k!=-1;k=edge[k].next){
        dfs(edge[k].to);
    }
    out[u] = cnt;
}

int lowbit(int x){
    return x&(-x);
}

int sum(int x){
    int res = 0;
    while(x){
    	res+=d[x];
    	x-=lowbit(x);
	}
    return res;
}

void update(int x,int v){
	while(x<=cnt){
		d[x]+=v;
		x+=lowbit(x);
	}
}

int main()
{
    int n,m;
    while(scanf("%d",&n)!=EOF){
        init();
        for(int i=0;i<n-1;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            addEdge(u,v,tot);
        }
        dfs(1);
        /*for(int i=1;i<=n;i++){
            printf("%d %d\n",in[i],out[i]);
        }*/
        for(int i=1;i<=n;i++){
            have[i] = 1;
            update(in[i],1);
        }
        int q;
        scanf("%d",&q);
        while(q--){
            char op;
            int x;
            scanf(" %c%d",&op,&x);  /*注意字符读入时,前面加上空格,这样可避免回车符的读入  
									当然字符也可以以字符串的形式读入,取第一个字符,这样前面就无需加空格了*/ 
            //cout<<"op="<<op<<"x="<<endl;
            if(op=='Q'){
                printf("%d\n",sum(out[x])-sum(in[x]-1));  //[in[x],out[x]]
            }else{
                if(have[x]) update(in[x],-1);
                else update(in[x],1);
                have[x] ^= 1;   //have[x] = !have[x];
            }
        }
    }
    return 0;
}

2.树链修改,单点查询

  将一条树链x,y上的所有点的权值加v。这个问题可以等价为:

  1).x到根节点的链上所有节点权值加v。

  2).y到根节点的链上所有节点权值加v。

  3).lca(x,y)到根节点的链上所有节点权值和减v。

  4).fa(lca(x,y))到根节点的链上所有节点权值和减v。  

  上面四个操作可以归结为:节点x到根节点链上所有节点的权值加减v。修改节点x权值,当且仅当y是x的祖先节点时,x对y的值有贡献。

  所以节点y的权值可以转化为节点y的子树节点贡献和。从贡献和的角度想:这就是点修改,区间和查询问题。

  修改树链x,y等价于add(l[x],v),add(l[y],v),add(l[lca(x,y)],-v),add(l[fa(lca(x,y))],-v)。

  查询:get_sum(r[x])-get_sum(l[x]-1)

  用树状数组或线段树即可。

3.树链修改,子树和查询

  树链修改部分同上一问题。下面考虑子树和查询问题:前一问是从贡献的角度想,子树和同理。

  对于节点y其到根节点的权值和,考虑其子节点x的贡献:w[x]*(deep[x]-deep[y]+1) = w[x]*(deep[x]+1)-w[x]*deep[y] 

  所以节点y的子树和为:

  

  ps:公式中的v[i]为手误,应为w[i]。

  所以用两个树状数组或线段树即可:

    第一个维护∑w[i]*(deep[i]+1):支持操作单点修改,区间和查询。(这也就是问题2)

    第二个维护∑ w[i]:支持操作单点修改,区间查询。(这其实也是问题2)

4.单点更新,树链和查询

  树链和查询与树链修改类似,树链和(x,y)等于下面四个部分和相加:

  1).x到根节点的链上所有节点权值加。

  2).y到根节点的链上所有节点权值加。

  3).lca(x,y)到根节点的链上所有节点权值和的-1倍。

  4).fa(lca(x,y))到根节点的链上所有节点权值和的-1倍。

  所以问题转化为:查询点x到根节点的链上的所有节点权值和。

  修改节点x权值,当且仅当y是x的子孙节点时,x对y的值有贡献。

  差分前缀和,y的权值等于dfs中[1,l[y]]的区间和。

  单点修改:add(l[x],v),add(r[x]+1,-v);

5.子树修改,单点查询

  修改节点x的子树权值,在dfs序上就是区间修改,单点权值查询就是单点查询。

  区间修改,单点查询问题:树状数组或线段树即可;

6.子树修改,子树和查询

  题目等价与区间修改,区间查询问题。用树状数组或线段树即可。

7.子树修改,树链查询

  树链查询同上,等价为根节点到y节点的链上所有节点和问题。

  修改节点x的子树权值,当且仅当y是x的子孙节点时(或y等于x),x对y的值有贡献。

  x对根节点到y节点的链上所有节点和的贡献为:w[x]*(deep[y]-deep[x]+1)=w[x]*deep[y]-w[x]*(1-deep[x])

  同问题三,用两个树状数组或线段树即可。

 

参考文章:树 DFS序 详解[完全版]

初学dfs序 

DFS序详解

dfs序的七个基本问题来源如下

作者:weeping 
出处:www.cnblogs.com/weeping/ 
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 28
    点赞
  • 78
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值