题目概述
链接:https://vjudge.net/problem/POJ-3321
题目还是比较好懂的。给你一个树,初始状态下所有节点值都为1,输入C时,将该点从0变成1或1变成0。输入Q时,求该节点下所有节点的值的和。很容易联想到线段树或树状数组。
思路分析
这道题的难点在于,他所给的是一棵树,而不像之前给的是一个一维的序列,我们要做的就是对单点或区间的查询、修改。比较下之前的题型和这道题,他们的区别是这道题我们并不明确每一个点他所管辖的区间是哪一部份,所以,我们可以采取以下方法得到这个信息:
首先,用存树的方法(邻接表、链式向前星等)存下这个树,然后,dfs遍历一次这棵树,这个过程中,用一个时间戳tim,记录下进去该节点的时间t1,还有遍历完这个点及其子树的时间t2,这样,这两个时间点组成的区间[t1,t2]便是他所管辖的范围。
拿样例来说:
样例是一颗以1为根,2、3分别为其儿子的树。
那么记录完之后的结果为:
t1[1]=1,t2[1]=3,管辖范围为[1,3]
t1[2]=2,t2[2]=2,管辖范围为[2,2]
t1[3]=3,t2[3]=3,管辖范围为[3,3]
这就给我们一个信息:我们查询这个节点的时候,需要查询哪一部分的和。然后,他在查找的时候,要求的就是它管辖范围的右侧的前缀和减去左侧-1(需要包括左侧的数)的前缀和。
而生长的摘掉的操作就是单点修改了。我们是改变它的左端点还是右端点呢?答案是左端点,我的理解是:因为我们都会设置左端点<=右端点,所以,左端点的值他才能唯一地代表这个点。例如:还是样例,右端点对于端点1和3来说都是3,会指定不明,所以,我们应该是对左端点进行add,而且这样求前缀和的时候才更加合乎逻辑。
完整代码
#include <iostream>
#include <cstdio>
using namespace std;
#define lowbit(x) ((x)&-(x))
#define change ch
const int maxn = 100005;
struct Edge {
int to;
int next;
};
int n;
Edge e[maxn << 1];
int tree[maxn] = { 0 };
int head[maxn] = { 0 };
int vis[maxn] = { 0 };
int ch[maxn] = { 0 };
int l[maxn] = { 0 };
int r[maxn] = { 0 };
int tim;
int cnt = 0;
void add(int x, int y) {
e[cnt].to = y;
e[cnt].next = head[x];
head[x] = cnt++;
}
void dfs(int u) {
vis[u] = 1;
l[u] = ++tim;
for (int i = head[u]; i != -1; i = e[i].next) {
int v = e[i].to;
if (vis[v]) continue;
dfs(v);
}
r[u] = tim;
}
void add1(int x, int num) {
while (x <= n) {
tree[x] += num;
x += lowbit(x);
}
}
int sum(int x) {
int ans = 0;
while (x > 0) {
ans += tree[x];
x -= lowbit(x);
}
return ans;
}
int main()
{
scanf("%d", &n);
for (int i = 0; i <= n; i++) head[i] = -1;
for (int i = 1; i < n; i++) {
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
add(b, a);
}
dfs(1);
for (int i = 1; i <= n; i++) {
add1(i, 1);
change[i] = 1;
}
int cs;
scanf("%d", &cs);
while (cs--) {
char cmd[2];
cin >> cmd;
if (cmd[0] == 'C') {
int x;
scanf("%d", &x);
if (change[x]) {
change[x] = 0;
add1(l[x], -1);
}
else {
change[x] = 1;
add1(l[x], 1);
}
}
if (cmd[0] == 'Q') {
int x;
scanf("%d", &x);
printf("%d\n",sum(r[x]) - sum(l[x] - 1));
}
}
return 0;
}