博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
DFS序
阅读量:5068 次
发布时间:2019-06-12

本文共 14215 字,大约阅读时间需要 47 分钟。

DFS序

总结:

1、树转化为线性:将树通过dfs转化为线性结构,这就是dfs序,和树链剖分有点相似

2、普通树转化为线段树:记录每个节点构成的树(子树)的起点和终点,起点是自己,这样每个点就构成了一个区间,然后对区间的操作就和线段树和树状数组一样了。

3、DFS序主要用来做子树的更新,因为DFS序中子树都是连续的。时间复杂度看树的储存结构,O(1)。

4、树的储存可以用图来存储,图的数组模拟邻接矩阵:小兵应该先武装好自己,然后再加入队伍,队长负责周转(管最前面一个)。

 

 

详解:

给定一棵n个节点的树,m次查询,每次查询需要求出某个节点深度为h的所有子节点。

 

对于这个问题如果试图去对每个节点保存所有深度的子节点,在数据大的时候内存会吃不消;或者每次查询的时候去遍历一遍,当数据大的时候,时间效率会非常低。

 

此时如果使用dfs序维护树结构就可以轻松地解决这个问题。

 

作为预处理,首先将将树的所有节点按深度保存起来,每个深度的所有节点用一个线性结构保存,每个深度的节点相对顺序要和前序遍历一致。

 

然后从树的根节点进行dfs,对于每个节点记录两个信息,一个是dfs进入该节点的时间戳in[id],另一个是dfs离开该节点的时间戳out[id]。

 

最后对于每次查询,求节点v在深度h的所有子节点,只需将深度为h并且dfs进入时间戳在in[v]和out[v]之间的所有节点都求出来即可,由于对于每个深度的所有节点,相对顺序和前序遍历的顺序以致,那么他们的dfs进入时间戳也是递增的,于是可以通过二分搜索求解。

 

分析

Step 1:

如下图,可以看到,由于普通的树并不具有区间的性质,所以在考虑使用线段树作为解题思路时,需要对给给定的数据进行转化,首先对这棵树进行一次dfs遍历,记录dfs序下每个点访问起始时间与结束时间,记录起始时间是前序遍历,结束时间是后序遍历,同时对这课树进行重标号。

 

Step 2:

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

 

         那么此时,每个节点对应了一个区间,而且可以看到,每个节点对应的区间正好“管辖”了它子树所有节点的区间,那么对点或子树的操作就转化为了对区间的操作。

【PS: 如果对树的遍历看不懂的话,不妨待会对照代码一步一步调试,或者在纸上模拟过程~】

Step 3:

         这个时候,每次对节点进行更新或者查询,就是线段树和树状数组最基本的实现了…

 

树是一种非线性结构,一般而言,我们总是想办法将其转化为线性结构,将树上操作包括子树操作、路径操作等转化为数组上的区间操作,从而在一个较为理想的复杂度内加以解决。将树“拍平”的方法有很多,例如欧拉序、等。实际上欧拉序也是在DFS过程中得到的。不过通常而言,我们所说的DFS序是指:每个节点进出栈的时间序列

这里写图片描述 

考虑上图中树的DFS序,应为 
这里写图片描述

其中,每个节点均会出现2次,第一次是进入DFS的时刻,第二次是离开DFS的时刻。分别称之为InOut。在区间操作中,如果某个节点出现了2次,则该节点将被“抵消”。所以通常会将Out时刻对应的点设置为负数。

树的DFS序列有几个有用的性质:

  1. 任意子树是连续的。例如子树BEFK,在序列中对应BEEFKKFB;子树CGHI,在序列中对应连续区间CGGHHIIC
  2. 任意点对(a,b)之间的路径,可分为2种情况,首先令lcaab的最近公共祖先: 
    1. lcaab之一,则ab之间的In时刻的区间或者Out时刻区间就是其路径。例如AK之间的路径就对应区间ABEEFK,或者KFBCGGHHIICA
    2. lca另有其人,则ab之间的路径为In[a]Out[b]之间的区间或者In[b]Out[a]之间的区间。另外,还需额外加上lca!!!考虑EK路径,对应为EFK再加上B。考虑EH之间的路径,对应为EFKKFBCGGH再加上A

利用这些性质,可以利用DFS序列完成子树操作和路径操作,同时也有可能将应用到树上从而得到。

 

 

代码:

dfs序是处理树上问题很重要的一个武器,主要能够解决对于一个点,它的子树上的一些信息的维护。 

就比如那天百度之星round1A的1003题,就是dfs序+线段树维护每个点到0点的距离,然后对于每个点的更新,只需要更新它和它的子树上的点到0点的距离,查询的话就是它的子树上的最大值即可 
我的dfs序开的空间就是n,因为只在入的地方时间戳++,出来的地方时间戳不变,线段树的每个节点应该是时间戳

1 void dfs(int u,int fa){ 2     p1[u]=++ti; 3     dfsnum[ti]=u; 4     for(int i=head[u];i!=-1;i=edge[i].next){ 5         int v=edge[i].v; 6         if(v==fa) continue; 7         dfs(v,u); 8     } 9     p2[u]=ti;//时间戳不变,空间为O(n)10 }

 

例题:

需要在树上完成3类操作,单点更新,子树更新,以及根到指定节点的路径查询。利用性质1以及性质2.1即可完成,连LCA都无需求出。对整个DFS序列使用线段树进行维护,注意到整个序列实际上有正有负,因此额外用一个域来表示正数的个数。

4034: [HAOI2015]树上操作

Time Limit: 10 Sec  Memory Limit: 256 MB
Submit: 6323  Solved: 2094
[][][]

Description

有一棵点数为 N 的树,以点 1 为根,且树点有边权。然后有 M 个
操作,分为三种:
操作 1 :把某个节点 x 的点权增加 a 。
操作 2 :把某个节点 x 为根的子树中所有点的点权都增加 a 。
操作 3 :询问某个节点 x 到根的路径中所有点的点权和。

Input

第一行包含两个整数 N, M 。表示点数和操作数。接下来一行 N 个整数,表示树中节点的初始权值。接下来 N-1 
行每行三个正整数 fr, to , 表示该树中存在一条边 (fr, to) 。再接下来 M 行,每行分别表示一次操作。其中
第一个数表示该操作的种类( 1-3 ) ,之后接这个操作的参数( x 或者 x a ) 。
 

Output

对于每个询问操作,输出该询问的答案。答案之间用换行隔开。

 

Sample Input

5 5
1 2 3 4 5
1 2
1 4
2 3
2 5
3 3
1 2 1
3 5
2 1 2
3 3

Sample Output

6
9
13

HINT

 

 对于 100% 的数据, N,M<=100000 ,且所有输入数据的绝对值都不会超过 10^6 。

 

Source

1 #include 
2 #include
3 #include
4 #include
5 using namespace std; 6 7 int const SIZE = 100100; 8 typedef long long weight_t; 9 10 struct edge_t{
//边 11 int to; 12 int next; 13 }Edge[SIZE<<1];//双向的 所以*2 14 int Vertex[SIZE]; 15 int ECnt; 16 weight_t W[SIZE]; 17 18 //数组模拟邻接矩阵:顶点记录的是下一个点,顶点负责转运 19 inline void mkEdge(int a,int b){
//造a-b这条边 20 //先把节点的信息补充好,再建立联系 21 //先武装好自己,然后再图报效 22 //每个队的长官记录每个队节点的分配信息 23 Edge[ECnt].to = b; 24 Edge[ECnt].next = Vertex[a]; 25 Vertex[a] = ECnt++; 26 27 Edge[ECnt].to = a; 28 Edge[ECnt].next = Vertex[b]; 29 Vertex[b] = ECnt++; 30 } 31 32 int InIdx[SIZE],OutIdx[SIZE]; 33 int InOut[SIZE<<1]; 34 int NewIdx[SIZE<<1]; 35 int NCnt; 36 37 void dfs(int node,int parent){ 38 NewIdx[NCnt] = node; 39 InOut[NCnt] = 1; 40 InIdx[node] = NCnt++; 41 for(int next=Vertex[node];next;next=Edge[next].next){ 42 int son = Edge[next].to; 43 if ( son != parent ) dfs(son,node); 44 } 45 NewIdx[NCnt] = node; 46 InOut[NCnt] = -1; 47 OutIdx[node] = NCnt++; 48 } 49 50 int N; 51 weight_t StSum[SIZE<<3]; 52 weight_t Lazy[SIZE<<3]; 53 int Flag[SIZE<<3];//The count of the positive number in the range 54 55 inline int lson(int x){
return x<<1;} 56 inline int rson(int x){
return lson(x)|1;} 57 58 inline void _pushUp(int t){ 59 StSum[t] = StSum[lson(t)] + StSum[rson(t)]; 60 Flag[t] = Flag[lson(t)] + Flag[rson(t)]; 61 } 62 63 inline void _pushDown(int t){ 64 if ( 0LL == Lazy[t] ) return; 65 66 weight_t& x = Lazy[t]; 67 68 int son = lson(t); 69 StSum[son] += Flag[son] * x; 70 Lazy[son] += x; 71 72 son = rson(t); 73 StSum[son] += Flag[son] * x; 74 Lazy[son] += x; 75 76 x = 0LL; 77 } 78 79 void build(int t,int s,int e){ 80 Lazy[t] = 0LL; 81 if ( s == e ){ 82 StSum[t] = InOut[s] * W[NewIdx[s]]; 83 Flag[t] = InOut[s]; 84 return; 85 } 86 87 int m = ( s + e ) >> 1; 88 build(lson(t),s,m); 89 build(rson(t),m+1,e); 90 _pushUp(t); 91 } 92 93 void modify(int t,int s,int e,int a,int b,weight_t delta){ 94 if ( a <= s && e <= b ){ 95 StSum[t] += Flag[t] * delta; 96 Lazy[t] += delta; 97 return; 98 } 99 100 _pushDown(t);101 int m = ( s + e ) >> 1;102 if ( a <= m ) modify(lson(t),s,m,a,b,delta);103 if ( m < b ) modify(rson(t),m+1,e,a,b,delta);104 _pushUp(t);105 }106 107 weight_t query(int t,int s,int e,int a,int b){108 if ( a <= s && e <= b ){109 return StSum[t];110 }111 112 _pushDown(t);113 114 weight_t ret = 0LL;115 int m = ( s + e ) >> 1;116 if ( a <= m ) ret += query(lson(t),s,m,a,b);117 if ( m < b ) ret += query(rson(t),m+1,e,a,b);118 return ret;119 }120 121 inline weight_t query(int x){122 return query(1,1,N<<1,1,InIdx[x]);123 }124 125 inline void modify(int x,weight_t delta){126 modify(1,1,N<<1,InIdx[x],InIdx[x],delta);127 modify(1,1,N<<1,OutIdx[x],OutIdx[x],delta);128 }129 130 inline void modifySubtree(int x,weight_t delta){131 modify(1,1,N<<1,InIdx[x],OutIdx[x],delta);132 }133 134 //这里树的储存方式用的是图里面的数组仿邻接矩阵 135 inline void initTree(int n){136 ECnt = NCnt = 1;137 //vertex是顶点,这就是存储边的方式 138 fill(Vertex,Vertex+n+1,0);139 }140 141 int M;142 bool read(){143 if ( EOF == scanf("%d%d",&N,&M) ) return false;144 145 initTree(N);146 for(int i=1;i<=N;++i)scanf("%lld",W+i);//读权值 147 148 int a,b;149 for(int i=1;i

 

 

HDU 3887 

题目传送门: 
问你对于每个节点,它的子树上标号比它小的点有多少个 
子树的问题,dfs序可以很轻松的解决,因为点在它的子树上,所以在线段树中,必定在它的两个时间戳的区间之间,所以我们只需要从小到大考虑,它的区间里有多少个点已经放了,然后再把它放进去。很容易的解决了 
代码:

1 #include   2 #include 
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
9 #include
10 #include
11 #include
12 #include
13 #include
14 #include
15 #pragma comment(linker, "/STACK:102400000,102400000") 16 17 using namespace std; 18 #define MAX 500005 19 #define MAXN 6005 20 #define maxnode 15 21 #define sigma_size 30 22 #define lson l,m,rt<<1 23 #define rson m+1,r,rt<<1|1 24 #define lrt rt<<1 25 #define rrt rt<<1|1 26 #define middle int m=(r+l)>>1 27 #define LL long long 28 #define ull unsigned long long 29 #define mem(x,v) memset(x,v,sizeof(x)) 30 #define lowbit(x) (x&-x) 31 #define pii pair
32 #define bits(a) __builtin_popcount(a) 33 #define mk make_pair 34 #define limit 10000 35 36 //const int prime = 999983; 37 const int INF = 0x3f3f3f3f; 38 const LL INFF = 0x3f3f; 39 const double pi = acos(-1.0); 40 //const double inf = 1e18; 41 const double eps = 1e-8; 42 const LL mod = 1e9+7; 43 const ull mx = 133333331; 44 45 /*****************************************************/ 46 inline void RI(int &x) { 47 char c; 48 while((c=getchar())<'0' || c>'9'); 49 x=c-'0'; 50 while((c=getchar())>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0'; 51 } 52 /*****************************************************/ 53 54 struct Edge{ 55 int v,next; 56 }edge[MAX*2]; 57 int head[MAX]; 58 int tot; 59 int p1[MAX]; 60 int p2[MAX]; 61 int ti; 62 int sum[MAX<<2]; 63 64 void init(){ 65 mem(head,-1); 66 tot=0;ti=0; 67 } 68 69 void add_edge(int a,int b){ 70 edge[tot]=(Edge){b,head[a]}; 71 head[a]=tot++; 72 } 73 74 void dfs(int u,int fa){ 75 p1[u]=++ti; 76 for(int i=head[u];i!=-1;i=edge[i].next){ 77 int v=edge[i].v; 78 if(v==fa) continue; 79 dfs(v,u); 80 } 81 p2[u]=ti; 82 } 83 84 void build(int l,int r,int rt){ 85 sum[rt]=0; 86 if(l==r) return; 87 middle; 88 build(lson); 89 build(rson); 90 } 91 92 void pushup(int rt){ 93 sum[rt]=sum[lrt]+sum[rrt]; 94 } 95 96 void update(int l,int r,int rt,int pos,int d){ 97 if(l==r){ 98 sum[rt]+=d; 99 return;100 }101 middle;102 if(pos<=m) update(lson,pos,d);103 else update(rson,pos,d);104 pushup(rt);105 }106 107 int query(int l,int r,int rt,int L,int R){108 if(L<=l&&r<=R) return sum[rt];109 middle;110 int ret=0;111 if(L<=m) ret+=query(lson,L,R);112 if(R>m) ret+=query(rson,L,R);113 return ret;114 }115 116 int main(){117 int n,p;118 while(cin>>n>>p&&n){119 init();120 for(int i=1;i

 

poj 3321 

题目传送门: 
这题是一开始告诉你树上每个节点都有1个苹果,然后你对一个节点操作,如果有苹果,就拿走,没苹果,就放上,然后询问你以x为根的子树上有多少个苹果。 
dfs序水题,代码:

1 #include   2 #include 
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
9 #include
10 #include
11 #include
12 #include
13 #include
14 #include
15 #pragma comment(linker, "/STACK:102400000,102400000") 16 17 using namespace std; 18 #define MAX 500005 19 #define MAXN 6005 20 #define maxnode 15 21 #define sigma_size 30 22 #define lson l,m,rt<<1 23 #define rson m+1,r,rt<<1|1 24 #define lrt rt<<1 25 #define rrt rt<<1|1 26 #define middle int m=(r+l)>>1 27 #define LL long long 28 #define ull unsigned long long 29 #define mem(x,v) memset(x,v,sizeof(x)) 30 #define lowbit(x) (x&-x) 31 #define pii pair
32 #define bits(a) __builtin_popcount(a) 33 #define mk make_pair 34 #define limit 10000 35 36 //const int prime = 999983; 37 const int INF = 0x3f3f3f3f; 38 const LL INFF = 0x3f3f; 39 const double pi = acos(-1.0); 40 //const double inf = 1e18; 41 const double eps = 1e-8; 42 const LL mod = 1e9+7; 43 const ull mx = 133333331; 44 45 /*****************************************************/ 46 inline void RI(int &x) { 47 char c; 48 while((c=getchar())<'0' || c>'9'); 49 x=c-'0'; 50 while((c=getchar())>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0'; 51 } 52 /*****************************************************/ 53 54 struct Edge{ 55 int v,next; 56 }edge[MAX*2]; 57 int head[MAX]; 58 int tot; 59 int p1[MAX]; 60 int p2[MAX]; 61 int ti; 62 int sum[MAX<<2]; 63 64 void init(){ 65 mem(head,-1); 66 tot=0;ti=0; 67 } 68 69 void add_edge(int a,int b){ 70 edge[tot]=(Edge){b,head[a]}; 71 head[a]=tot++; 72 } 73 74 void dfs(int u,int fa){ 75 p1[u]=++ti; 76 for(int i=head[u];i!=-1;i=edge[i].next){ 77 int v=edge[i].v; 78 if(v==fa) continue; 79 dfs(v,u); 80 } 81 p2[u]=ti; 82 } 83 84 void pushup(int rt){ 85 sum[rt]=sum[lrt]+sum[rrt]; 86 } 87 88 void build(int l,int r,int rt){ 89 if(l==r){ 90 sum[rt]=1; 91 return; 92 } 93 middle; 94 build(lson); 95 build(rson); 96 pushup(rt); 97 } 98 99 void update(int l,int r,int rt,int pos){100 if(l==r){101 sum[rt]=sum[rt]^1;102 return;103 }104 middle;105 if(pos<=m) update(lson,pos);106 else update(rson,pos);107 pushup(rt);108 }109 110 int query(int l,int r,int rt,int L,int R){111 if(L<=l&&r<=R) return sum[rt];112 middle;113 int ret=0;114 if(L<=m) ret+=query(lson,L,R);115 if(R>m) ret+=query(rson,L,R);116 return ret;117 }118 119 int main(){120 int n;121 cin>>n;122 init();123 for(int i=1;i

 

 

CodeForces 620E 

题目传送门: 
给你一棵树,每个节点都有颜色,然后问你子树上有多少种不同的颜色 
考虑到颜色一共只有60种,所以可以直接二进制记录,每个节点记录这个节点的颜色,然后区间直接左右子树或起来,然后对x为根的子树都变成c颜色,区间赋值,需要pushdown,pushup。 
有个坑点就是需要记录每个时间戳是哪个点,然后在build线段树的时候,sum[rt]=c[dfsnum[l]],这个坑点我错了好久,因为线段树的节点的下标应该是时间戳。GG,仍需努力 
代码:

1 #include   2 #include 
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
9 #include
10 #include
11 #include
12 #include
13 #include
14 #include
15 #pragma comment(linker, "/STACK:102400000,102400000") 16 17 using namespace std; 18 #define MAX 400005 19 #define MAXN 6005 20 #define maxnode 15 21 #define sigma_size 30 22 #define lson l,m,rt<<1 23 #define rson m+1,r,rt<<1|1 24 #define lrt rt<<1 25 #define rrt rt<<1|1 26 #define middle int m=(r+l)>>1 27 #define LL long long 28 #define ull unsigned long long 29 #define mem(x,v) memset(x,v,sizeof(x)) 30 #define lowbit(x) (x&-x) 31 #define pii pair
32 #define bits(a) __builtin_popcount(a) 33 #define mk make_pair 34 #define limit 10000 35 36 //const int prime = 999983; 37 const int INF = 0x3f3f3f3f; 38 const LL INFF = 0x3f3f; 39 const double pi = acos(-1.0); 40 //const double inf = 1e18; 41 const double eps = 1e-8; 42 const LL mod = 1e9+7; 43 const ull mx = 133333331; 44 45 /*****************************************************/ 46 inline void RI(int &x) { 47 char c; 48 while((c=getchar())<'0' || c>'9'); 49 x=c-'0'; 50 while((c=getchar())>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0'; 51 } 52 /*****************************************************/ 53 54 struct Edge{ 55 int v,next; 56 }edge[MAX*2]; 57 int head[MAX]; 58 int tot; 59 int c[MAX]; 60 int p1[MAX]; 61 int p2[MAX]; 62 int ti; 63 int dfsnum[MAX]; 64 LL sum[MAX<<2]; 65 int col[MAX<<2]; 66 void init(){ 67 mem(head,-1); 68 tot=0;ti=0; 69 } 70 71 void add_edge(int a,int b){ 72 edge[tot]=(Edge){b,head[a]}; 73 head[a]=tot++; 74 } 75 76 void dfs(int u,int fa){ 77 p1[u]=++ti; 78 dfsnum[ti]=u; 79 for(int i=head[u];i!=-1;i=edge[i].next){ 80 int v=edge[i].v; 81 if(v==fa) continue; 82 dfs(v,u); 83 } 84 p2[u]=ti; 85 } 86 87 void pushup(int rt){ 88 sum[rt]=sum[lrt]|sum[rrt]; 89 } 90 91 void pushdown(int rt){ 92 if(col[rt]){ 93 col[lrt]=col[rrt]=col[rt]; 94 sum[lrt]=sum[rrt]=(1LL<
m) update(rson,L,R,d);120 pushup(rt);121 }122 123 LL query(int l,int r,int rt,int L,int R){124 if(L<=l&&r<=R) return sum[rt];125 middle;126 LL ret=0;127 pushdown(rt);128 if(L<=m) ret|=query(lson,L,R);129 if(R>m) ret|=query(rson,L,R);130 return ret;131 }132 133 int main(){134 //freopen("in.txt","r",stdin);135 int n,m;136 while(cin>>n>>m){137 init();138 for(int i=1;i<=n;i++) scanf("%d",&c[i]);139 for(int i=1;i

 

转载于:https://www.cnblogs.com/Renyi-Fan/p/8244003.html

你可能感兴趣的文章
VMWARE虚拟机无法访问的三种方法分析
查看>>
enq: SQ - contention
查看>>
cer证书签名验证
查看>>
ant 安装
查看>>
新手Python第一天(接触)
查看>>
iOS中ARC内部原理
查看>>
【bzoj1029】[JSOI2007]建筑抢修
查看>>
synchronized
查看>>
你不得不了解的应用容器引擎---Docker
查看>>
easyui datagrid 弹出页面会出现两个上下滚动条处理办法!
查看>>
迭代器和生成器
查看>>
codevs 1080 线段树练习
查看>>
JS模块化库seajs体验
查看>>
Android内核sysfs中switch类使用实例
查看>>
POJ2288 Islands and Bridges(TSP:状压DP)
查看>>
[No0000195]NoSQL还是SQL?这一篇讲清楚
查看>>
IOS开发UI篇--UITableView的自定义布局==xib布局
查看>>
【深度学习】caffe 中的一些参数介绍
查看>>
Python-Web框架的本质
查看>>
Unrecognized Windows Sockets error: 0: JVM_Bind 异常解决办法
查看>>