樹的深搜序的一些簡單用途

VictoryCzt發表於2018-12-29

首先,對於一棵樹有很多序,比如寬搜序,深搜的先序遍歷,後序遍歷等。這裡我們只介紹樹的深搜序和尤拉序。


  • 尤拉序

一棵樹的尤拉序,就是在深搜遍歷這棵樹的時候,每次訪問到這個節點就將其加入棧,例如下面這個圖:

sl

它的尤拉序就是:[1,2,4,4,2,1,3,5,5,3,6,6,3,1][1,2,4,4,2,1,3,5,5,3,6,6,3,1]

用途就是可以用rmqrmqststablest-stable來快速求lcalca,詳解見這裡【RMQ-LCA

  • dfs序

博主這裡主要總結dfs序。

這個就是利用一個時間戳,在深搜過程中,記錄訪問到這個節點時的時間和退出訪問到這個節點的時間。

例如上面那張圖,訪問順序就是:
sl

對於每個節點,第一次訪問到它的時間戳和退出訪問的時間戳就是:

1=(1,6)2=(2,3)3=(3,3)4=(4,6)5=(5,5)6=(6,6) 1=(1,6) \\ 2=(2,3) \\ 3=(3,3) \\ 4=(4,6) \\ 5=(5,5) \\ 6=(6,6)

然後我們按照時間戳大小將點排序就可以得到這顆樹的dfs序了:[1,2,4,3,5,6][1,2,4,3,5,6]

得到這個序的程式碼非常簡單:

struct edge{
	int to,last;
	edge(){}
	edge(int a,int b):to(a),last(b){}
}g[M<<1];
int head[M],cnt;//前向星(連結串列)存圖
int dfn[M],out[M],tim;//進入時的時間戳,出來時的時間戳,時間戳
int stk[M];//儲存dfs序

void find_dfs(int a,int fa){
	dfn[a]=++tim;stk[tim]=a;
	//開始訪問,記錄時間戳,並加入dfs序
	for(int i=head[a];i;i=g[i].last){
		if(g[i].to==fa) continue;
		find_dfs(g[i].to,a);
	}
	out[a]=tim;//結束訪問記錄時間戳
}

那麼這個序有什麼用呢?

下面來看具體例題:

Loj144-DFS 序 1

題意見loj題面。

其實我們可以直接樹鏈剖分+線段樹就可以搞定題目中的操作,但是每次操作複雜度是O(log2n)O(log^2n)的,所以在10610^6的資料範圍內我們很難不卡常跑過。

所以我們考慮,使用dfs序。

首先我們看上面那個圖的dfs序:
[1,2,4,3,5,6] [1,2,4,3,5,6]

對於一號點的子樹,範圍剛好就是dfs序上的161\sim 6的點,而對於三號點的子樹,範圍又剛好是464\sim 6的點,也就剛好對應了訪問到它的時間戳到結束訪問的時間戳的範圍,在dfs序上都是連續的一段,所以我們可以考慮對於這個序列,我們建一個線段樹。

那麼操作11,給一個節點加上一個值,就是線段樹的單點修改,而操作22,求一個節點的子樹和,那麼就是線上段樹上詢問這個點的訪問到它的時間戳到結束訪問它的時間戳這個區間的和。

由於只有單點修改,區間查詢,我們可以使用常數更小的樹狀陣列來實現:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lowbit(a) ((a)&(-(a)))
#define ll long long
using namespace std;
const int M=1e6+10;
int n,m,rot;
struct ss{
	int to,last;
	ss(){}
	ss(int a,int b):to(a),last(b){}
}g[M<<1];
int head[M],cnt;
void add(int a,int b){
	g[++cnt]=ss(b,head[a]);head[a]=cnt;
	g[++cnt]=ss(a,head[b]);head[b]=cnt;
}

ll val[M];
int dfn[M],rf[M],tim,out[M];//rf就為dfs序
void dfs(int a,int b){
	rf[dfn[a]=++tim]=a;
	for(int i=head[a];i;i=g[i].last){
		if(g[i].to==b) continue;
		dfs(g[i].to,a);
	}
	out[a]=tim;
}
ll bit[M];
void build(){
	for(int i=1;i<=n;i++)bit[i]=val[i]-val[i-lowbit(i)];
}
void Add(int a,ll v){
	for(;a<=n;a+=lowbit(a))bit[a]+=v;
}
ll query(int a){
	ll res=1;
	for(;a;a-=lowbit(a))res+=bit[a];
	return res;
}
ll Query(int p){
	return query(out[p])-query(dfn[p]-1);
}
ll v[M];int a,b,opt;
int main(){
	scanf("%d%d%d",&n,&m,&rot);
	for(int i=1;i<=n;i++)scanf("%lld",&v[i]);
	for(int i=1;i<n;i++){
		scanf("%d%d",&a,&b);
		add(a,b);
	}
	dfs(rot,0);
	for(int i=1;i<=n;i++){val[i]=val[i-1]+v[rf[i]];}
	build();
	while(m--){
		scanf("%d%d",&opt,&a);
		if(opt==1){
			scanf("%d",&b);
			Add(dfn[a],b);
		}else{
			printf("%lld\n",Query(a));
		}
	}
	return 0;
}

loj125-DFS 序 2

這個也就是和上面那個一樣了,只不過變成了區間修改區間查詢,可以用差分和兩個樹狀陣列實現,但是線段樹寫起了要無腦簡單些,所以博主用線段樹實現的:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int M=1e6+10;

int n,m,rot;
struct ss{
	int to,last;
	ss(){}
	ss(int a,int b):to(a),last(b){}
}g[M<<1];
int head[M],cnt;
void add(int a,int b){
	g[++cnt]=ss(b,head[a]);head[a]=cnt;
	g[++cnt]=ss(a,head[b]);head[b]=cnt;
}
ll val[M];
int dfn[M],rf[M],out[M],tim;
void dfs(int a,int b){
	rf[dfn[a]=++tim]=a;
	for(int i=head[a];i;i=g[i].last){
		if(g[i].to==b) continue;
		dfs(g[i].to,a);
	}
	out[a]=tim;
}

ll sum[M<<2],lazy[M<<2];
void pushup(int o){
	sum[o]=sum[o<<1]+sum[o<<1|1];
}
void pushdown(int o,int l,int r,int mid){
	if(!lazy[o]) return;
	sum[o<<1]+=1ll*(mid-l+1)*lazy[o];
	sum[o<<1|1]+=1ll*(r-mid)*lazy[o];
	lazy[o<<1]+=lazy[o];
	lazy[o<<1|1]+=lazy[o];
	lazy[o]=0;
}
void build(int o,int l,int r){
	if(l==r){
		sum[o]=val[rf[l]];
		return;
	}
	int mid=l+r>>1;
	build(o<<1,l,mid);
	build(o<<1|1,mid+1,r);
	pushup(o);
}
void update(int o,int l,int r,int L,int R,ll v){
	if(L<=l&&r<=R){
		lazy[o]+=v;
		sum[o]+=1ll*(r-l+1)*v;
		return;
	}
	int mid=l+r>>1;
	pushdown(o,l,r,mid);
	if(L<=mid)update(o<<1,l,mid,L,R,v);
	if(R>mid)update(o<<1|1,mid+1,r,L,R,v);
	pushup(o);
}
ll query(int o,int l,int r,int L,int R){
	if(L<=l&&r<=R){
		return sum[o];
	}
	int mid=l+r>>1;
	pushdown(o,l,r,mid);
	if(R<=mid) return query(o<<1,l,mid,L,R);
	else if(L>mid) return query(o<<1|1,mid+1,r,L,R);
	else return query(o<<1,l,mid,L,R)+query(o<<1|1,mid+1,r,L,R);
}
int a,b,opt;
int main(){
	scanf("%d%d%d",&n,&m,&rot);
	for(int i=1;i<=n;i++)scanf("%lld",&val[i]);
	for(int i=1;i<n;i++){
		scanf("%d%d",&a,&b);
		add(a,b);
	}
	dfs(rot,0);
	build(1,1,n);
	while(m--){
		scanf("%d%d",&opt,&a);
		if(opt==1){
			scanf("%d",&b);
			update(1,1,n,dfn[a],out[a],b);
		}else{
			printf("%lld\n",query(1,1,n,dfn[a],out[a]));
		}
	}
	return 0;
}

loj146-DFS 序 3

loj147-DFS 序 4

這兩個題目,主要要用到樹上差分了,博主後面再詳細講解先咕咕咕一會兒

相關文章