樹的深搜序的一些簡單用途
首先,對於一棵樹有很多序,比如寬搜序,深搜的先序遍歷,後序遍歷等。這裡我們只介紹樹的深搜序和尤拉序。
- 尤拉序
一棵樹的尤拉序,就是在深搜遍歷這棵樹的時候,每次訪問到這個節點就將其加入棧,例如下面這個圖:
它的尤拉序就是:
用途就是可以用的來快速求,詳解見這裡【RMQ-LCA】
- dfs序
博主這裡主要總結dfs序。
這個就是利用一個時間戳,在深搜過程中,記錄訪問到這個節點時的時間和退出訪問到這個節點的時間。
例如上面那張圖,訪問順序就是:
對於每個節點,第一次訪問到它的時間戳和退出訪問的時間戳就是:
然後我們按照時間戳大小將點排序就可以得到這顆樹的dfs序了:
得到這個序的程式碼非常簡單:
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;//結束訪問記錄時間戳
}
那麼這個序有什麼用呢?
下面來看具體例題:
題意見loj題面。
其實我們可以直接樹鏈剖分+線段樹就可以搞定題目中的操作,但是每次操作複雜度是的,所以在的資料範圍內我們很難不卡常跑過。
所以我們考慮,使用dfs序。
首先我們看上面那個圖的dfs序:
對於一號點的子樹,範圍剛好就是dfs序上的的點,而對於三號點的子樹,範圍又剛好是的點,也就剛好對應了訪問到它的時間戳到結束訪問的時間戳的範圍,在dfs序上都是連續的一段,所以我們可以考慮對於這個序列,我們建一個線段樹。
那麼操作,給一個節點加上一個值,就是線段樹的單點修改,而操作,求一個節點的子樹和,那麼就是線上段樹上詢問這個點的訪問到它的時間戳到結束訪問它的時間戳這個區間的和。
由於只有單點修改,區間查詢,我們可以使用常數更小的樹狀陣列來實現:
#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;
}
這個也就是和上面那個一樣了,只不過變成了區間修改區間查詢,可以用差分和兩個樹狀陣列實現,但是線段樹寫起了要無腦簡單些,所以博主用線段樹實現的:
#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;
}
這兩個題目,主要要用到樹上差分了,博主後面再詳細講解先咕咕咕一會兒
相關文章
- 二叉搜尋樹的後序遍歷序列
- 簡單聊聊運維監控的其他用途運維
- 【樹01】對二叉樹前序/中序/後序遍歷演算法的一些思考二叉樹演算法
- 樹的DFS序
- B 樹的簡單認識
- ElasticSearch 簡單的 搜尋 聚合 分析Elasticsearch
- [劍指offer] 二叉搜尋樹的後序遍歷序列
- JZ-023-二叉搜尋樹的後序遍歷序列
- B+ 樹的簡單認識
- 一些簡單的JavaScript加密/解密JavaScript加密解密
- 【leetcode 簡單】 第六十八題 二叉搜尋樹的最近公共祖先LeetCode
- 【資料結構與演算法】二叉搜尋樹的簡單封裝資料結構演算法封裝
- 自制簡單的詩歌搜尋系統
- sql簡單入門的一些操作SQL
- Linux下一些操作的簡單整理Linux
- SHELL中常用的一些簡單命令
- leetcode 530. Minimum Absolute Difference in BST二叉搜尋樹的最小絕對差 (簡單)LeetCode
- The order of a Tree (二叉搜尋樹+先序遍歷)
- 深搜dfs
- C#單例模式的用途C#單例模式
- 劍指 Offer 33. 二叉搜尋樹的後序遍歷序列
- 字典樹(字首樹)簡單實現
- EasyFind for Mac操作簡單的檔案搜尋工具Mac
- 樹的拓撲序計數
- 一些常見的簡單最佳化
- 一些簡單的快捷鍵與DOS命令
- 資料庫簡單的一些原理概念資料庫
- 一些常用的html、css、js的簡單應用HTMLCSSJS
- 順序審批流的簡單程式碼實現
- JavaScript深拷貝的一些坑JavaScript
- 樹狀陣列入門(簡單的原理講解)陣列
- 線段樹簡單思路
- SimpleAISearch:C# + DuckDuckGo 實現簡單的AI搜尋AIC#Go
- L2_006樹的遍歷(後序+中序->前序/層序)
- 分享一些簡單的 Laravel 編碼實踐Laravel
- 一些免費、操作簡單的工具軟體
- kali下一些代理工具的簡單描述
- 總結的一些簡單面試題目面試題