[每日一题]: poj 3321 Apple Tree
题目:
考察点:
DFS序、线段树、树状数组、区间操作的数据结构
DFS序不了解的戳这里:
https://www.cnblogs.com/prjruckyone/p/12754936.html
线段树不了解的戳这里:
https://www.cnblogs.com/prjruckyone/p/12293330.html
侃侃:
题目中有两种操作:
1、如果说原来某个节点有苹果,那么就吃掉它,如果这个节点没有苹果,就种上一个。
2、查询一下以当前节点为根节点的树有多少个苹果(子节点的数量)
那么怎么做呢?
说到统计子节点的数量,你可能会想到并查集,合并到一起计算一下子节点的数量,但是这里
还有一个操作 1 ,说明这是一个动态的问题。知道了这一点后,那么我们想一下,支持动态
修改的数据结构有哪些呢?
线段树、树状数组、分块大法,and so on.......
(原谅我的菜,了解这么多)
如果你熟悉 DFS 序的话,就会很清楚的知道 DFS 序路径中重复走到一个点时正好可以
代表一棵子树(详情请点击上方 DFS 序的链接)
不熟悉的话正好学习一下(嘻嘻)
这时候我们就可以将每个子树都表示成在 DFS 序中的一个区间,如果我们求子树的大小
实际上就是求区间和,吃掉或者种苹果实际上就是单点修改。
到这里就比较明了了。
Code:
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 10;
struct node {
int l,r,sum;
} tr[maxn << 2];
int head[maxn << 1],Next[maxn << 1],ver[maxn << 1];
int w[maxn],st[maxn],ed[maxn];
int tot,idx,n,u,v,m,t;
void add(int u,int v) {
ver[++ tot] = v,Next[tot] = head[u],head[u] = tot;
return ;
}
// st[]: 记录起点 ed[]: 记录终点,两者组合成一个子树的区间
// st 和 ed 都是存储的 DFS 序
void DFS(int u,int fa) {
st[u] = ++ idx;
for(int i = head[u]; i ; i = Next[i]) {
int y = ver[i];
if(y != fa) { // 由于是树,所以走过的点不能再走,也可以用标记数组进行标记一下
DFS(y,u); // 根据深度优先搜索,递归到最深处再回溯
}
}
ed[u] = idx;
return ;
}
void pushup(int u) {
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
return ;
}
void build(int u,int l,int r) {
tr[u].l = l,tr[u].r = r;
if(l == r) {
tr[u].sum = w[r];
return ;
}
int mid = l + r >> 1;
build(u << 1,l,mid);
build(u << 1 | 1,mid + 1,r);
pushup(u);
return ;
}
// 更新方式 1
void update1(int u,int x,int v) {
if(tr[u].l == tr[u].r) {
tr[u].sum += v;
w[x] += v;
return ;
}
int mid = tr[u].l + tr[u].r >> 1;
if(x <= mid) update(u << 1,x,v);
else update(u << 1 | 1,x,v);
pushup(u);
return ;
}
// 更新方式 2
void update2(int u,int x) {
if(tr[u].l == tr[u].r) {
tr[u].sum ^= 1;
return ;
}
int mid = tr[u].l + tr[u].r >> 1;
if(x <= mid) update(u << 1,x,v);
else update(u << 1 | 1,x,v);
pushup(u);
return ;
}
int query(int u,int L,int R) {
if(L <= tr[u].l && tr[u].r <= R) {
return tr[u].sum;
}
int sum = 0;
int mid = tr[u].l + tr[u].r >> 1;
if(L <= mid) sum = query(u << 1,L,R);
if(R > mid) sum += query(u << 1 | 1,L,R);
return sum;
}
int main(void) {
scanf("%d",&n);
for(int i = 1; i < n; i ++) {
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
w[i] = 1; // 初始化数组,刚开始每个节点上都有一个苹果
}
w[n] = 1;
DFS(1,-1);
build(1,1,idx); // 在 DFS 序的基础上建立一颗线段树
scanf("%d",&m);
while(m --) {
char op;
int x;
cin >> op >> x;
// 单点修改
if(op == 'C') {
// st 是 DFS 序 (-1 标记为吃掉了这个苹果)
// 我们线段树修改的是 DFS 序, w[]数组是原序列
if(w[st[x]]) update1(1,st[x],-1);
else update1(1,st[x],1);
//update2(1,st[x]);
} else {
// 区间查询
printf("%d\n",query(1,st[x],ed[x]));
}
}
return 0;
}
后记:
感谢师傅大佬的倾囊而助,帮我找到一个调了很久的 bug。
另外,线段树我们会,DFS序也了解,和在一起怎么就搞不
出来了呢?
还是得多练习一下综合性的题目。
如有解释的不到位的地方,或者哪里有问题的地方,欢迎
各位大佬指出,在此万分感谢。
如果说年轻人未来是一场盛宴的话,那么我首先要有赴宴的资格。