[每日一题]: 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序也了解,和在一起怎么就搞不
出来了呢?
还是得多练习一下综合性的题目。
如有解释的不到位的地方,或者哪里有问题的地方,欢迎    
各位大佬指出,在此万分感谢。
posted @ 2020-04-22 19:28  IceSwords  阅读(160)  评论(0编辑  收藏  举报