数据结构专题1

林靖轩PPT

树的遍历与树上的序

树的遍历

void dfs(int u,int f){
	for(auto v:mp[u]){
        if(v==f)continue;//防止重复访问
        dfs(v,u);
    }
}

树上的dfs序

一般有两种dfs序,通过长度进行分类:

  1. n个节点,这是最简单最常用的一种
  2. 2 ∗ n − 1 2*n-1 2n1 个节点,用的不多,但还是有一些性质

n个点

求法
int in[N],seq[N],out[N],idc; 
void dfs(int u,int f){
    seq[++idc] = u;
    in[u] = idc;
	for(auto v:mp[u]){
        if(v==f)continue;
        dfs(v,u);
    }
    out[u] = idc;
}

idc = 0;
dfs(root,root);
性质
每个节点和 DFS 序中的位置一一对应,即 u 所在序列中位置是 in[u],
序列下标 i 对应节点是 seq[i] ,
所以有 seq[in[u]] = u
每棵子树在 DFS 序中是连续的,即 u 节点代表的子树的所有节点
都在 [in[u],out[u]] 中。

2*n-1个点

求法
int in[N],seq[N],out[N],idc; 
void dfs(int u,int f){
    seq[++idc] = u;
    in[u] = idc;
	for(auto v:mp[u]){
        if(v==f)continue;
        dep[v]=dep[u]+1;
        dfs(v,u);
        seq[++idc] = u;
    }
}
idc = 0;
dfs(root,root);
性质
可以看成将每条边放入队列里两次,然后在最初根会被多算一次,所以最后 idc = 2*m+1 , 而树的边数与点数有 m = n-1 所以 idc = 2*n-1
u和v的lca即是[in[u],in[v]]这一段中的点的深度最浅的那个

树dfs序列常见问题

1. 单点改,子树求和

描述:树的每个结点都有权值。两个操作:单点修改结点权值,查询子树权值之和.

按照第一种方法建立子树深搜序,那么 [ i n [ u ] , o u t [ u ] ] [in[u],out[u]] [in[u],out[u]] 就表示以 u u u​ 为根节点的子树对应的深搜序. 这样子的话,就可以用树状数组维护。

2. 单点改,子树颜色种类

描述:树的每个节点都有颜色。两个操作:单点修改结点颜色,查询子树颜色种类数量

按照第一种方法建立树的深搜序,把序列分成一段一段的,每段的颜色都相同。建立深搜序列的树状数组。对于每一段颜色,每个点加1,相邻两个结点的最近公共祖先减1.

查询以 u u u 为根的子树颜色种类数量,仍然是 [ i n [ u ] , o u t [ u ] ] [in[u],out[u]] [in[u],out[u]] 的区间求和.

3. 单点修改,路径查询

描述:树的每个节点都有权值。单点修改结点权值,查询结点到根的路径权值之和.

按照第一种方法建立树的深搜序,树状数组维护深搜序,到根节点路径之和的差分数组。那么它影响的,是以 u u u​ 为根节点的子树的答案,子树所有节点到根节点路径之和都增加了 Δ w [ u ] \Delta w[u] Δw[u]​,即 u u u​ 权值的改变量。修改结点 u u u​ 时, a d d ( i n [ u ] , d ) , a d d ( o u t [ u ] + 1 , − d ) add(in[u], d),add(out[u] + 1,-d) add(in[u],d),add(out[u]+1,d)​.

查询 u u u 到根节点路径之和时,即 s u m ( i n [ u ] ) sum(in[u]) sum(in[u]) 即可.

变形

查询任意两节点的路径权值之和。

4. 查询结点到根路径的权值第 K K K 大结点(无修改操作)

在深搜的时候,建立第一种深搜序,并且建立权值可持久化线段树,每一个子结点 v v v 的版本信息从父结点 u u u 更新而来。查询结点 u u u 的时候,只需要查询 r o o t [ u ] root[u] root[u]​ 对应的线段树即可

5. 查询子树权值第 K K K 大的结点(无修改操作)

建立第一种深搜序,建立可持久化权值线段树,每一个结点 u u u 版本信息从深搜序为 i n [ u ] − 1 in[u] - 1 in[u]1 的线段树更新而来。查询的时候,查询 r o o t [ o u t [ u ] ] − r o o t [ i n [ u ] − 1 ] root[out[u]] - root[in[u]-1] root[out[u]]root[in[u]1]​ 对应的线段树即可。

6. 深度为序

查询在以 u u u 为根的子树中,到达 u u u 距离不超过 k k k 的结点中结点权值最小值。

方法:建立一个按照深度为序的树序列(可以理解为层次优先遍历),并且标记第一种深搜序。建立一个可持久化权值线段树,每一层作为一个版本信息,从上一层的版本更新而来.

线段树维护的序列就是深搜序的序列,把 i n [ u ] in[u] in[u]​ 对应的线段树结点权值修改为 u u u​ 对应的点权。比如编号为 x x x​ 的节点,就要把 r o o t [ d e p [ x ] ] root[dep[x]] root[dep[x]]​ 版本的线段树的 i n [ x ] in[x] in[x]​ 对应的结点权值修改为 w [ x ] w[x] w[x]​。这样子查询 u u u​ 的时候,只需查询 r o o t [ d e p [ u ] ] root[dep[u]] root[dep[u]]​ 在区间 [ i n [ u ] , o u t [ u ] ] [in[u],out[u]] [in[u],out[u]]​ 的最小值即可。因为深度大于 d e p [ u ] dep[u] dep[u]​ 的结点的信息不会存在于 r o o t [ d e p [ u ] ] root[dep[u]] root[dep[u]]​ 里面,而深度小于 d e p [ u ] dep[u] dep[u]​ 节点信息不会存在于区间 [ i n [ u ] , o u t [ u ] ] [in[u],out[u]] [in[u],out[u]]​ 之中。

例:

F. Subtree Minimum Query

题意:给一棵树,每个节点有点权,强制在线询问:给定节点 u u u,问在以 u u u 为根的子树中,到 u u u 距离不超过 k k k 的点中,点权最小值是多少?

#include
using namespace std;
const int N = 100010, M = N * 2;
const int INF = 1e9+7;
int h[N], e[M], ne[M], nidx;
int w[N];
int n, rt, m;
inline void add(int a, int b)
{
    e[nidx] = b, ne[nidx] = h[a], h[a] = nidx++;
}
int dep[N], in[N], out[N], cnt, max_dep;
vector<int> dep_order[N];

void dfs(int u, int fa)
{
    //printf("Im here\n");
    dep[u] = dep[fa] + 1;
    dep_order[dep[u]].push_back(u);
    max_dep = max(max_dep, dep[u]);

    in[u] = ++cnt;
    for(int i = h[u]; i != -1; i = ne[i])
    {
        int v = e[i];
        if(v == fa) continue;
        dfs(v, u);
    }
    out[u] = cnt;
}

struct node
{
    int l, r;
    int mmin;
}tr[N * 25];
int root[N], idx;
void pushup(int p)
{
    tr[p].mmin = min(tr[tr[p].l].mmin, tr[tr[p].r].mmin);
}

int build(int l, int r)
{
    //注意 build 的时候把 mmin 设成 INF
    int p = ++idx;
    if(l == r)
    {
        tr[p].mmin = INF;
        return p;
    }
    int mid = l + r >> 1;
    tr[p].l = build(l, mid), tr[p].r = build(mid + 1, r);
    pushup(p);
    return p;
}

int insert(int p, int l, int r, int id, int x)
{
    int q = ++idx;
    tr[q] = tr[p];
    if(l == r)
    {
        tr[q].mmin = x;
        return q;
    }
    int mid = l + r >> 1;
    if(id <= mid) tr[q].l = insert(tr[p].l, l, mid, id, x);
    else tr[q].r = insert(tr[p].r, mid + 1, r, id, x);
    pushup(q);
    return q;
}

int query(int p, int l, int r, int L, int R)
{
    if(L <= l && r <= R) return tr[p].mmin;
    int mid = l + r >> 1;
    int res = 1e9;
    if(L <= mid) res = query(tr[p].l, l, mid, L, R);
    if(R > mid) res = min(res, query(tr[p].r, mid + 1, r, L, R));
    return res;
}

int main()
{
    memset(h, -1, sizeof h);
    scanf("%d%d", &n, &rt);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d", &w[i]);
    }
    for(int i = 1; i < n; i++)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b), add(b, a);
    }
    dfs(rt, 0);
    
    root[0] = build(1, n);
    for(int i = 1; i <= max_dep; i++)
    {
        root[i] = root[i - 1];
        for(auto u : dep_order[i])
        {
            //insert 里面别写成 root[i - 1]
            root[i] = insert(root[i], 1, n, in[u], w[u]);
        }
    }

    scanf("%d", &m);
    int ans = 0;
    for(int i = 1; i <= m; i++)
    {
        int u, k;
        scanf("%d%d", &u, &k);
        u = (u + ans) % n + 1, k = (k + ans) % n;
        //printf("** k = %d u = %d, in[u] = %d, out[u] = %d, dep[u] = %d\n", k, u, in[u], out[u], dep[u]);
        ans = query(root[min(max_dep, dep[u] + k)], 1, n, in[u], out[u]);
        printf("%d\n", ans);
    }
    return 0;
}

你可能感兴趣的:(ACM题目整理,数据结构,深度优先,算法)