树的DFS序

基础概念

树的DFS序列就是说: 树的每一个节点在DFS中进出栈的时间序列。

具体来说就是对树从根开始进行深搜,按搜到的时间顺序把所有节点排队。

就比如
image

上面这棵树,它的一个DFS序就是:

1 4 6 6 3 9 9 3 4 7 7 2 5 5 8 8 2 1

注意两点:

  1. 一棵树的DFS序不唯一
    因为深搜的时候选择哪个子节点的顺序是不一样的。

  2. 对于一棵树进行DFS序,需要把回溯的时候的节点编号也记录一下,因此DFS序的长度是2N
    这就是为什么每个数字在DFS序中会出现两遍的原因。

性质
一个数字两次出现的位置所夹的区间,正好是以这个数为根的一个子树。

比如:
截取上面dfs序的2的两个位置的区间为:
2 8 8 5 5 2
那么2为根的子树是:2 8 5

经典用法:
配合树状数组进行区间修改,单点查询

代码实现

代码实现很简单,就是从根节点开始深搜,然后按顺序打标记就可以了。

但是打标记的地方可以有很多:

  1. id:DFS序列
  2. in: 以u为根节点的子树的在dfs序列的起始位置。
  3. 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序为\(dfn[u]+v-1\)的节点
代码:

#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的权值为 \(v_i\)

给 m 个操作,操作有两种类型:

  1. a x :表示将节点 a 的权值加上 x

  2. a :表示求 a 节点的子树上所有节点的和(包括 a 节点本身)

分析:
我们就在dfs序上使用树状数组,
对于操作一,单点修改
对于操作二,区间查询\([dfn[u]\,\,,\,\,dfn[u]+siz[u]-1]\)

代码:

#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;
}

原文

posted @ 2022-07-26 09:12  kingwzun  阅读(268)  评论(0编辑  收藏  举报