DFS序详解→→→树形转化为线型

 dfs序:

每个节点在dfs深度优先遍历中的进出栈的时间序列,dfs序可以把一棵树区间化,即可以求出每个节点的管辖区间。

我们先定义两个数组,in[x],out[x]。

in[x]为dfs进入结点x时的时间戳,out[x]为dfs离开结点x时的时间戳

dfs从根结点开始,每个结点分别记录两个信息:in[x],out[x]

看下面的这个树:

他的dfs序就是    1 2 5 5 2 3 3 4 6 6 7 7 4 1

很明显就是节点按照dfs的顺序来排列的嘛

那么这个顺序我们怎么保存下来呢

我们这里我们就要用到上面提到的 时间戳数组 in[x] 、out[x]了

其实这算是一个板子

int tim=0;
void dfs(int x,int pre){
	in[x]=++tim;//进的时间戳
	for(int i=head[x];~i;i=pp[i].next){
		int to=pp[i].to;
		if(to==pre)continue;
		dfs(to,x);
	}
	out[x]=tim;//出的时间戳
} 

可以根据代码模拟一下,就获得下面的效果图

è¿éåå¾çæè¿°

我们把点的进出时间戳表示成一个区间,这样其实就将它转化为线型的结构了

其实这时的每个节点控制的就是一段区间,而这段区间则包括了他和他的所有子节点,仔细思考一下就理解了

那么我们用这个能干嘛呢?(我现在学的只是基础,等到学的多的时候一定补上)

现在学的就是联系线段树或者树状数组,对树上的点进行单点或者区间的修改与查询操作操作

而每一个点的两个时间戳就是控制的区间的两个端点

最经典的例题: POJ-3321-Apple Tree  AC之后送你一个苹果

给你一棵树,树上每个节点都有1个苹果,然后让你对某一个节点进行操作,如果有苹果就拿走,没苹果就放上,然后询问你以x为根的子树上共有多少个苹果,怎么办?直接dfs暴力吗,多次遍历求和肯定被T,所以我们不得不采取点措施了就

由于是单点修改、区间求和,树状数组肯定就会比线段树简单一点了,我们就在这里小试一下牛刀吧!

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define mem(a,b) memset(a,b,sizeof(a))
#define ll long long
using namespace std;
const int inf=0x3f3f3f3f;
const int mm=1e5+10;

int n,m;
int in[mm],out[mm];
int tim;
int book[mm];//标记某个节点上有没有苹果 
int c[mm];

int head[mm<<1];
struct node{
	int from,to;
	int next;
}pp[mm<<1];
int cnt;
void add(int x,int y){
	pp[cnt].to=y;
	pp[cnt].next=head[x];
	head[x]=cnt++;
}

void dfs(int x,int pre){//求得dfs序 
	in[x]=++tim;
	for(int i=head[x];~i;i=pp[i].next){
		int to=pp[i].to;
		if(to==pre)continue;
		dfs(to,x);
	}
	out[x]=tim;
} 

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

void change(int pos,int k){
	while(pos<=n){
		c[pos]+=k;
		pos+=lbt(pos);
	}
}

int sum(int pos){
	int res=0;
	while(pos>0){
		res+=c[pos];
		pos-=lbt(pos);
	}
	return res;
}

int main()
{
	mem(head,-1);
	int x,y;
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		book[i]=1;//每棵树上都有苹果 
		change(i,1);//树状数组赋值先 
	}	
	for(int i=1;i

 

你可能感兴趣的:(dfs序,树状数组)