POJ 3321 Apple Tree

题目传送门
讲解视频

一、题目大意

给一棵树,原来每个叶子节点上都有苹果,然后有\(2\)种操作,一是问以\(x\)为根节点的子树有多少苹果,二是更改某点的苹果数量。

二、解题思路

做法就是用\(dfs\)序把树形结构转化成线性结构也就是区间,然后用线段树维护。

先要求出每个点的\(in\)\(out\)值,然后就给每个点分配了一个新编号了,就是它的\(in[i]\),然后\(dfs\)序有个特点就是以\(x\)为根节点的子树里的节点的\(dfs\)序是连续的,所以\(in[x]\)$out[x]$就对应着这颗子树中所有的点,查询的话对$in[x]$\(out[x]\)区间操作。

三、实现代码


#include <iostream>
#include <string.h>
#include <stdio.h>
#include <vector>
#include <map>
#include <queue>
#include <algorithm>
#include <math.h>
#include <cstdio>

using namespace std;

const int N = 1e5 + 10, M = N << 1;
//邻接表
int e[M], h[N], idx, ne[M];
void add(int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// dfs序专用
int in[N], out[N], tot;

//线段树结构体
struct Node {
    int l, r;
    int sum;
} tr[N << 2];

//向上更新父节点的子树苹果和
void pushup(int u) {
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}

//构建线段树
void build(int u, int l, int r) {
    tr[u] = {l, r};
    if (l == r) {
        tr[u].sum = 1; //最初的时候每个叶子节点上都是有苹果的
        return;
    }
    int mid = (l + r) >> 1;
    build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
    //向上更新父节点信息
    pushup(u);
}

//在以u为根的子树中修改x的值
void modify(int u, int x) {
    if (tr[u].l == tr[u].r) {
        tr[u].sum ^= 1; //异或就是将1变0,将0变1。
        return;
    }
    int mid = (tr[u].l + tr[u].r) >> 1;
    if (x <= mid)
        modify(u << 1, x);
    else
        modify(u << 1 | 1, x);
    pushup(u);
}

//查询区间值
int query(int u, int l, int r) {
    if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
    int mid = tr[u].l + tr[u].r >> 1;
    int ans;
    if (r <= mid)
        ans = query(u << 1, l, r);
    else if (l > mid)
        ans = query(u << 1 | 1, l, r);
    else
        ans = query(u << 1, l, r) + query(u << 1 | 1, l, r);
    return ans;
}

//记录dfs序
void dfs(int u, int fa) {
    in[u] = ++tot; // dfs序中u节点进入 时间戳
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == fa) continue; //不走回头路
        dfs(j, u);
    }
    out[u] = tot; // dfs序中u节点退出 时间戳
}

int main() {
    int n, m;
    scanf("%d", &n);
    memset(h, -1, sizeof h);
    for (int i = 1; i < n; i++) {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b), add(b, a);
    }
    //记录dfs序
    dfs(1, 0);

    //构建线段树
    build(1, 1, n);

    scanf("%d", &m);
    while (m--) {
        char op[2]; //注意:一般字符都需要用char数组读入,可以过滤掉空格和回车
        int k;
        scanf("%s%d", &op, &k);
        if (*op == 'C')
            modify(1, in[k]); //原始第k个节点,对应线段树中第in[k]个节点
        else
            printf("%d\n", query(1, in[k], out[k])); //查询k这个点在in[k],out[k]区间内的sum和。
    }
    return 0;
}
posted @ 2022-04-27 11:31  糖豆爸爸  阅读(32)  评论(0编辑  收藏  举报
Live2D