树的DFS序
基础概念
树的DFS序列就是说: 树的每一个节点在DFS中进出栈的时间序列。
具体来说就是对树从根开始进行深搜,按搜到的时间顺序把所有节点排队。
就比如
上面这棵树,它的一个DFS序就是:
1 4 6 6 3 9 9 3 4 7 7 2 5 5 8 8 2 1
注意两点:
-
一棵树的DFS序不唯一。
因为深搜的时候选择哪个子节点的顺序是不一样的。 -
对于一棵树进行DFS序,需要把回溯的时候的节点编号也记录一下,因此DFS序的长度是2N。
这就是为什么每个数字在DFS序中会出现两遍的原因。
性质
一个数字两次出现的位置所夹的区间,正好是以这个数为根的一个子树。
比如:
截取上面dfs序的2的两个位置的区间为:
2 8 8 5 5 2
那么2为根的子树是:2 8 5
经典用法:
配合树状数组进行区间修改,单点查询
代码实现
代码实现很简单,就是从根节点开始深搜,然后按顺序打标记就可以了。
但是打标记的地方可以有很多:
- id:DFS序列
- in: 以u为根节点的子树的在dfs序列的起始位置。
- out:以u为根节点的子树的在dfs序列的结束位置。
很明显,如果我们打了标记in和out,那么我们就能快速找到任何一棵子树。
因为
in[u]和out[u]的位置所夹的区间,正好是以这个数为根的一个子树。
代码:
vector<int> g[N]; int id[N],len; int in[N],out[N]; void dfs(int u,int pre){ id[len++]=u; in[u]=len; for(int t:g[u]){ if(t!=pre) dfs(t,u); } out[u]=len; }
基础应用
Military Problem 模板题
题意:
给出n-1对关系(构成一棵树),q个询问,包含u,vu,v ,表示一个命令从u点下达,问在他的子树中第v个收到命令的节点编号
分析:
在这颗子树中,第v个收到命令的那就是dfs序为的节点
代码:
#include <vector> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int N = 1e5 + 10; vector<int> g[100010]; int dfs_xu[200020], len; int in[N], out[N], timestemp; bool vis[N]; void dfs(int u) { // if(vis[u]) return; // vis[u]=true; dfs_xu[++len] = u; in[u] = len; for (int t : g[u]) { dfs(t); } out[u] = len; } int main() { int n; cin >> n; int m; cin >> m; vector<int> head; for (int i = 2; i <= n; i++) { int t; cin >> t; g[t].push_back(i); } dfs(1); // for (int i = 1; i <= len; i++) // { // cout << dfs_xu[i] <<endl; // } for (int i = 0; i < m; i++) { int u, k; cin >> u >> k; if(k>out[u]-in[u]+1){ cout<<"-1"<<endl; } else cout<<dfs_xu[in[u]+k-1]<<endl; } }
选点 性质应用
题意:
有一棵n个节点的二叉树,1为根节点,每个节点有一个值wi。现在要选出尽量多的点。
对于任意一棵子树,都要满足:
- 如果选了根节点的话,在这棵子树内选的其他的点都要比根节点的值大;
- 如果在左子树选了一个点,在右子树中选的其他点要比它小。
分析:
如果我们把这颗树拉成一条链,根据规律左节点>右节点>根节点,相当于在这个序列上求出一个最长下降子序列。
代码:
#include <vector> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int N = 1e5 + 10; int dfs_xu[200020], len; int in[N], out[N], timestemp; int w[N]; int f[N]; int ch[2][N]; void dfs(int u) { if(!u) return ; dfs_xu[++len] = w[u]; // in[u] = len; dfs(ch[1][u]); dfs(ch[0][u]); // out[u] = len; } int main() { int n;cin>>n; for(int i=1;i<=n;i++){ cin>>w[i]; } for(int i=1;i<=n;i++){ int x,y;cin>>x>>y; ch[0][i]=x; ch[1][i]=y; } dfs(1); int cnt=0; f[++cnt]=dfs_xu[1]; for(int i=2;i<=n;i++){ if(dfs_xu[i]>f[cnt]) f[++cnt]=dfs_xu[i]; else { int pos=lower_bound(f+1,f+cnt+1,dfs_xu[i])-f; f[pos]=dfs_xu[i]; } } cout<<cnt<<endl; return 0; }
求和 树状数组
题意:
已知有 n 个节点,有 n-1条边,形成一个树的结构。
给定一个根节点 k,每个节点都有一个权值,节点i的权值为
给 m 个操作,操作有两种类型:
-
a x :表示将节点 a 的权值加上 x
-
a :表示求 a 节点的子树上所有节点的和(包括 a 节点本身)
分析:
我们就在dfs序上使用树状数组,
对于操作一,单点修改
对于操作二,区间查询
代码:
#include <bits/stdc++.h> #define endl '\n' #define int long long using namespace std; typedef pair<int, int> pii; const int N = 1e6+10; const int INF = 0x3f3f3f3f3f; int n,m,k; int val[N]; int tr[N]; vector<int> g[N]; int in[N],out[N],dfn[N],cnt; int lowbit(int x){ return x&-x; } void add(int x,int c){ for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=c; } int sum(int x){ int res=0; for(int i=x;i;i-=lowbit(i)) res+=tr[i]; return res; } void get_dfs(int u,int fa){ dfn[++cnt]=val[u]; add(cnt,val[u]); in[u]=cnt; for(int v: g[u]){ if(v!=fa) get_dfs(v,u); } out[u]=cnt; } void slove() { cin>>n>>m>>k; for(int i=1;i<=n;i++){ cin>>val[i]; } for(int i=1;i<n;i++){ int u,v;cin>>u>>v; g[u].push_back(v); g[v].push_back(u); } // cout<<" sdff"<<endl; get_dfs(k,0); while(m--){ int kk;cin>>kk; // cout<<kk<<endl; if(kk==1){ int a,x;cin>>a>>x; add(in[a],x); // add(out[a],x); } else{ int a;cin>>a; int res=sum(out[a])-sum(in[a]-1); cout<<res<<endl; } } } signed main() { // int t; // cin >> t; // while (t--) slove(); return 0; }
原文
本文作者:kingwzun
本文链接:https://www.cnblogs.com/kingwz/p/16519499.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步