dfs序 题目小集

这篇博客集合了多个使用DFS序解决的树形问题,包括线段树、树状数组和莫比乌斯反演的应用。通过实例解析了如何利用DFS序对树进行操作,如改变节点权值、计算路径价值最大值、查询子树中与根权值互质的节点数量等。同时,介绍了在不同场景下,如HDU 5692、POJ 3321、HDU 5468、HDU 3887等题目中的解题思路和代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考

dfs序题目练习 ——樱花庄的龙之介大人
dfs序专题学习 ——Miracle_ma

HDU 5692 +线段树

题意

给定一棵树,有两种操作:
1. 改变某个点 x 的权值;
2. 定义路径的价值为其上所有点的权值之和,询问以 x 为根的子树内的点到根的路径的价值的最大值。

分析

(画好了图上传不了就很气)

        1
        /\
    2      3
    /\
4       5

对于这样的一棵树, dfs 一遍,记录下每个节点的时间戳以及以它为根的子树中时间戳最大的节点的时间戳,即

le[u]=cnt[u],ri[u]=max{cnt[v]|vu}

就上图而论,

id le ri
115
224
355
433
544

将原树映射到一棵线段树上,线段树的线段为 dfs 后每个节点的新编号(即时间戳,亦即 le[] ),维护的是该节点到根的路径的价值(比如说上例:[1,1]维护节点1到根的价值,[2,2]维护节点2到根的价值,[3,3]维护节点4到根的价值,[4,4]维护节点5到根的价值,[5,5]维护节点3到根的价值)。
而对点 x 的修改以及查询操作即为对区间[le[x],ri[x]]的修改与查询。

这么好用的嘛!

是的,我们考虑题目的要求:改变某个点 x 的权值;查询以 x 为根的子树内的点到根的路径的价值的最大值。
更改点 x 的权值 影响的 即 以 x 为根的子树内所有的点到根的路径的价值,所以 [le[x],ri[x]] 一整段都需要修改,查询的话即查询 [le[x],ri[x]] 一段的最大值。

希望讲清楚了 QWQ

Code

#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <bits/stdc++.h>
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define maxn 100010
typedef long long LL;
int le[maxn], ri[maxn], ne[maxn], tot, cnt, kas;
LL val[maxn], p[maxn], a[maxn];
struct node {
    int l, r; LL val, tag;
}tr[maxn * 4];
struct Edge {
    int to, ne;
    Edge(int a = 0, int b = 0): to(a), ne(b) {}
}edge[maxn * 2];
void add(int u, int v) {
    edge[tot] = Edge(v, ne[u]);
    ne[u] = tot++;
}
void init() {
    tot = cnt = 0;
    memset(ne, -1, sizeof(ne));
}
inline LL max(LL a, LL b) { return a > b ? a : b; }
inline int midi(int l, int r) { return l + r >> 1; }
inline void push_up(int rt) { tr[rt].val = max(tr[lson].val, tr[rson].val); }
inline void push_down(int rt) {
    if (tr[rt].tag) {
        tr[lson].tag += tr[rt].tag; tr[rson].tag += tr[rt].tag;
        tr[lson].val += tr[rt].tag; tr[rson].val += tr[rt].tag;
        tr[rt].tag = 0;
    }
}
void build(int rt, int l, int r) {
    tr[rt].l = l; tr[rt].r = r; tr[rt].tag = 0;
    if (l == r) { tr[rt].val = a[l]; return; }
    int mid = midi(l, r);
    build(lson, l, mid); build(rson, mid + 1, r);
    push_up(rt);
}
void dfs(int u, int fa, LL w) {
    le[u] = ri[u] = ++cnt;
    p[u] = w;
    for (int i = ne[u]; i != -1; i = edge[i].ne) {
        int v = edge[i].to;
        if (v == fa) continue;
        dfs(v, u, w + val[v]);
        ri[u] = max(ri[u], ri[v]);
    }
}
void modify(int rt, int l, int r, LL add) {
    if (tr[rt].l == l && tr[rt].r == r) {
        tr[rt].tag += add;
        tr[rt].val += add;
        return;
    }
    push_down(rt);
    int mid = midi(tr[rt].l, tr[rt].r);
    if (r <= mid) modify(lson, l, r, add);
    else if (l > mid) modify(rson, l, r, add);
    else { modify(lson, l, mid, add); modify(rson, mid + 1, r, add); }
    push_up(rt);
}
LL query(int rt, int l, int r) {
    if (tr[rt].l == l && tr[rt].r == r) return tr[rt].val;
    push_down(rt);
    int mid = midi(tr[rt].l, tr[rt].r);
    if (r <= mid) return query(lson, l, r);
    else if (l > mid) return query(rson, l, r);
    else return max(query(lson, l, mid), query(rson, mid + 1, r));
}
void work() {
    printf("Case #%d:\n", ++kas);
    init();
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i < n; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        add(u, v); add(v, u);
    }
    for (int i = 0; i < n; ++i) scanf("%lld", &val[i]);
    dfs(0, -1, val[0]);

    for (int i = 0; i < n; ++i) a[le[i]] = p[i];
    build(1, 1, n);
    while (m--) {
        int x, u; LL w;
        scanf("%d", &x);
        if (x == 0) {
            scanf("%d%lld", &u, &w);
            modify(1, le[u], ri[u], w - val[u]);
            val[u] = w;
        }
        else {
            scanf("%d", &u);
            printf("%lld\n", query(1, le[u], ri[u]));
        }
    }
}
int main() {
    freopen("in.txt", "r", stdin);
    int T;
    scanf("%d", &T);
    while (T--) work();
    return 0;
}

POJ 3321 +树状数组

麻烦移步本菜另一篇博文 poj 3321 Apple Tree 树状数组 dfs序

HDU 5468 +莫比乌斯反演

题意

给定一棵树,每个节点都有一个权值。对于每一个节点,问以它为根的子树中的点与它的权值互质的有多少个。

参考

AOQNRMGYXLMV 的博客

分析

即问

i=lr[(vall,vali)=1]

d|vallμ(d)i=lr[d|vali]

cnt[] 数组记录每个因子出现的个数,对于点 u ,比较回溯时的值和第一次访问时 cnt[] 数组的差值即可。

Code

#include <bits/stdc++.h>
#include <vector>
#define maxn 100000
#define maxm maxn + 10
using namespace std;
int mu[maxm], prime[maxm], cnt[maxm], kas, ans[maxm], ne[maxm], tot, val[maxm];
bool check[maxm];
vector<int> fac[maxm];
struct Edge {
    int to, ne;
    Edge(int a = 0, int b = 0) : to(a), ne(b) {}
}edge[maxn * 2];
void add(int u, int v) {
    edge[tot] = Edge(v, ne[u]);
    ne[u] = tot++;
}
void init() {
    int tot = 0; mu[1] = 1;
    for (int i = 1; i <= maxn; ++i) fac[i].push_back(1);
    for (int i = 2; i <= maxn; ++i) {
        if (!check[i]) {
            prime[tot++] = i;
            mu[i] = -1;
        }
        for (int j = 0; j < tot; ++j) {
            if (i * prime[j] > maxn) break;
            check[i * prime[j]] = true;
            if (i % prime[j] == 0) {
                mu[i * prime[j]] = 0;
                break;
            }
            mu[i * prime[j]] = -mu[i];
        }
        if (mu[i] != 0) {
            for (int j = i; j <= maxn; j += i) fac[j].push_back(i);
        }
    }
}
void dfs(int u, int fa) {
    vector<int> pre;
    ans[u] = 0;
    for (auto x : fac[val[u]]) {
        pre.push_back(cnt[x]);
        ++cnt[x];
    }
    for (int i = ne[u]; ~i; i = edge[i].ne) {
        int v = edge[i].to;
        if (v == fa) continue;
        dfs(v, u);
    }
    vector<int>& ve = fac[val[u]];
    int sz = ve.size();
    for (int i = 0; i < sz; ++i) {
        ans[u] += mu[ve[i]] * (cnt[ve[i]] - pre[i]);
    }
}
int n;
void work() {
    memset(cnt, 0, sizeof(cnt));
    memset(ne, -1, sizeof(ne));
    tot = 0;
    for (int i = 1; i < n; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        add(u, v); add(v, u);
    }
    for (int i = 1; i <= n; ++i) scanf("%d", &val[i]);
    dfs(1, -1);
    printf("Case #%d: %d", ++kas, ans[1]);
    for (int i = 2; i <= n; ++i) printf(" %d", ans[i]); printf("\n");
}
int main() {
    freopen("in.txt", "r", stdin);
    init();
    while (scanf("%d", &n) != EOF) work();
    return 0;
}

HDU 3887 +树状数组

题意

给定一棵树,对每个节点 i f(i) f(i) 为以 i 为根的子树中的编号 <i 的点的数量。

思路

求得 dfs 序后,维护一个树状数组,从大到小查询 f(i) ,查完一个扔一个,保证树状数组中存在的数都小于当前查询的数 i ,这样 [le[i],ri[i]] 内存在的数必定也都小于 i .

(哇这种从大到小查询啦从小到大插入啦的思想真的炒鸡重要QWQ!!!

Code

#include <bits/stdc++.h>
#define maxn 100010
using namespace std;
struct Edge {
    int to, ne;
    Edge(int a = 0, int b = 0) : to(a), ne(b) {}
}edge[maxn * 2];
int ans[maxn], le[maxn], ri[maxn], c[maxn], cnt, tot, ne[maxn];
void addEdge(int u, int v) {
    edge[tot] = Edge(v, ne[u]);
    ne[u] = tot++;
}
void dfs(int u, int fa) {
    le[u] = ri[u] = ++cnt;
    for (int i = ne[u]; ~i; i = edge[i].ne) {
        int v = edge[i].to;
        if (v == fa) continue;
        dfs(v, u);
        ri[u] = max(ri[u], ri[v]);
    }
}
int n, p;
inline int lowbit(int x) { return x & (-x); }
inline void add(int x, int del) { while (x <= n) c[x] += del, x += lowbit(x); }
inline int query(int x) { int ret = 0; while (x) ret += c[x], x -= lowbit(x); return ret; }
void work() {
    tot = cnt = 0;
    memset(ne, -1, sizeof(ne));
    memset(c, 0, sizeof(c));
    for (int i = 1; i < n; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        addEdge(u, v); addEdge(v, u);
    }
    dfs(p, -1);
    for (int i = 1; i <= n; ++i) add(i, 1);
    for (int i = n; i >= 1; --i) {
        ans[i] = query(ri[i]) - query(le[i]);
        add(le[i], -1);
    }
    printf("%d", ans[1]);
    for (int i = 2; i <= n; ++i) printf(" %d", ans[i]); printf("\n");
}
int main() {
    while (scanf("%d%d", &n, &p) != EOF && n + p) work();
    return 0;
}

update-2017.9.5

今天写 HDU 5877 Weak Pair dfs序 + 树状数组 + 离散化 时又回来看了看这里,觉得当时怕不是智障了...
就和上面那个莫比乌斯的题一样,进去的时候算一次,出来的时候算一次,就解决了啊0.0
当时愣是不会做去百度然后看到 从大到小查完一个扔一个 的思路觉得棒呆了…(虽然的确是很重要的思想),但这道题本身是很直白很简单的思路…

于是又写了一发

Code

#include <bits/stdc++.h>
#define maxn 100010
int c[maxn], ans[maxn], n, p, ne[maxn], tot;
struct Edge {
    int to, ne;
    Edge(int a = 0, int b = 0) : to(a), ne(b) {}
}edge[maxn * 2];
void addEdge(int u, int v) {
    edge[tot] = Edge(v, ne[u]);
    ne[u] = tot++;
}
int lowbit(int x) { return x & -x; }
void add(int x, int d) { while (x <= n) c[x] += d, x += lowbit(x); }
int query(int x) { int ret = 0; while (x) ret += c[x], x -= lowbit(x); return ret; }
void dfs(int u, int fa) {
    add(u, 1);
    int temp = query(u);
    for (int i = ne[u]; ~i; i = edge[i].ne) {
        int v = edge[i].to;
        if (v == fa) continue;
        dfs(v, u);
    }
    ans[u] = query(u) - temp;
}
void work() {
    tot = 0;
    memset(c, 0, sizeof c);
    memset(ne, -1, sizeof ne);
    for (int i = 1; i < n; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        addEdge(u, v); addEdge(v, u);
    }
    dfs(p, -1);
    printf("%d", ans[1]);
    for (int i = 2; i <= n; ++i) printf(" %d", ans[i]); printf("\n");
}
int main() {
    while (scanf("%d%d", &n, &p) != EOF && n + p) work();
    return 0;
}

CF 620E +线段树+bitmasks

题意

给定一棵树,每个节点都有涂有一种颜色(颜色总数 60 )。现有两种操作:
1. 将以 x 为根的子树全都涂成 c 色;
2. 询问以 x 为根的子树中有多少种颜色。

思路

十分裸的 dfs序 + 线段树。
(这种涂色问颜色数的本菜之前也在 线段树 lazy tag 小合集 -POJ2777写到过,虽然这次和当年(雾)做法并不一样)
这次看了 Tutorial 于是学习到了一发 bitmasks ,颜色数只有 60 ,于是可以用 long long 来表示,区间求颜色总数在这里就是求个或。
一个注意点:左移时要用 1LL << c

Code

#include <bits/stdc++.h>
#define maxn 400010
#define lson (rt << 1)
#define rson (rt << 1 | 1)
using namespace std;
typedef long long LL;
int col[maxn], c[maxn], le[maxn], ri[maxn], tot, cnt, ne[maxn];
struct Edge {
    int to, ne;
    Edge(int a = 0, int b = 0): to(a), ne(b) {}
}edge[maxn * 2];
struct node {
    int l, r; LL tag, mask;
}tr[maxn * 4];
inline int midi(int l, int r) { return l + r >> 1; }
void add(int u, int v) {
    edge[tot] = Edge(v, ne[u]);
    ne[u] = tot++;
}
void dfs(int u, int fa) {
    le[u] = ri[u] = ++cnt;
    for (int i = ne[u]; ~i; i = edge[i].ne) {
        int v = edge[i].to;
        if (v == fa) continue;
        dfs(v, u);
        ri[u] = max(ri[u], ri[v]);
    }
}
inline void push_up(int rt) {
    tr[rt].mask = tr[lson].mask | tr[rson].mask;
}
inline void push_down(int rt) {
    if (tr[rt].tag) {
        tr[lson].mask = tr[rson].mask = tr[lson].tag = tr[rson].tag = tr[rt].tag;
        tr[rt].tag = 0;
    }
}
int count(LL x) {
    int ret = 0;
    while (x) {
        ++ret;
        x &= (x - 1);
    }
    return ret;
}
void build(int rt, int l, int r) {
    tr[rt].l = l, tr[rt].r = r, tr[rt].tag = 0;
    if (l == r) { tr[rt].mask = (1LL << c[l]); return; }
    push_up(rt);
    int mid = midi(l, r);
    build(lson, l, mid); build(rson, mid +1, r);
    push_up(rt);
}
void modify(int rt, int l, int r, int c) {
    if (tr[rt].l == l && tr[rt].r == r) {
        tr[rt].mask = (1LL << c);
        tr[rt].tag = (1LL << c);
        return;
    }
    push_down(rt);
    int mid = midi(tr[rt].l, tr[rt].r);
    if (r <= mid) modify(lson, l, r, c);
    else if (l > mid) modify(rson, l, r, c);
    else { modify(lson, l, mid, c); modify(rson, mid + 1, r, c); }
    push_up(rt);
}
LL query(int rt, int l, int r) {
    if (count(tr[rt].mask) == 1) return tr[rt].mask;
    if (tr[rt].l == l && tr[rt].r == r) return tr[rt].mask;
    push_down(rt);
    int mid = midi(tr[rt].l, tr[rt].r);
    if (r <= mid) return query(lson, l, r);
    else if (l > mid) return query(rson, l, r);
    else return query(lson, l, mid) | query(rson, mid + 1, r);
    push_up(rt);
}
int n, m;
void work() {
    tot = cnt = 0; memset(ne, -1, sizeof(ne));
    for (int i = 1; i <= n; ++i) scanf("%d", &col[i]);
    for (int i = 1; i < n; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        add(u, v); add(v, u);
    }
    dfs(1, -1);
    for (int i = 1; i <= n; ++i) c[le[i]] = col[i] - 1;
    build(1, 1, n);
    while (m--) {
        int t, v, c;
        scanf("%d", &t);
        if (t == 1) {
            scanf("%d%d", &v, &c);
            modify(1, le[v], ri[v], c - 1);
        }
        else {
            scanf("%d", &v);
            printf("%d\n", count(query(1, le[v], ri[v])));
        }
    }
}
int main() {
    while (scanf("%d%d", &n, &m) != EOF) work();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值