题意:给一棵有根树,各节点编号为1到N,其中根的编号为1,各个节点上可能有1个苹果或者没有苹果。现在需要支持两种操作:
(1)改变一个节点的“苹果情况”,即:如果原来有则吃掉它,如果没有则长出一个。
(2)询问以某个节点为根的子树上一共有多少苹果。
操作数量为M。1 <= N,M <= 100000。
由于此题出现于“树状数组练习题”,所以我就仔细思考如何使用树状数组……
树状数组只能维护区间和,如何把一个子树的和转化为区间和呢?也就是如何把子树转化为区间呢?于是我们就想到了后根遍历序列。(其实前根遍历也可以,没有什么区别)后根遍历序列当中,一个节点的所有子孙以及他自己是连续出现的。那么对于一个节点,他对应的区间是哪一块呢?这个区间的结束位置肯定是他自己对应的位置(后根嘛),而起始位置可以先递归到他的孩子里面,然后让孩子返回自己的子树的起始位置,然后他把所有孩子的起始位置取最小即可。
然后就是标准的树状数组问题了。对于后根序列维护和查询区间和,这个就不说了……
还有一个比较麻烦的问题。可以想见,我们在进行各种操作的时候经常会遇到把“苹果树上编号”(以后称为num)与“后根序列中位置”,也就是“树状数组中编号”(以后称为id)相互转化的需要。(具体来说;维护后根序列即是id到num的映射,之后查询的时候给出的是num,需要转化为id之后再去树状数组里查询)因此我们要维护一个双向的映射,从num映射到id以及从id映射到num。
然后这就是我们的大致思路:输入之后,从1号节点后序遍历整棵树。遍历过程:对于每个节点,先递归遍历所有孩子,然后把后根序列curID++,维护自己id到num和num到id的映射,并记录自己的minID(子树区间起始位置),然后返回值为自己的minID。然后,对于每个操作,先把输入的num转成id,然后去维护或者查询树状数组即可。
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <vector>
#define INF 1000007
using namespace std;
int N,M;
vector<int> G[100007]; //邻接表
void AddEdge(int a, int b)
{
G[a].push_back(b);
G[b].push_back(a);
}
int minID[100007]={}; //[id]
int n2ID[100007]={}; //[1 ~ N]
int ID2n[100007]={}; //[1 ~ N]
/* postRoot: 后根遍历, 建立苹果树num到树状数组id的双向映射,
* 并维护苹果树中各节点minID
*/
bool vis[100007]={};
int tempID = 0;
int postRoot(int x) //后根遍历, x: num
{
vis[x] = true;
int cMinID = INF;
for(int i = 0; i < G[x].size(); i++)
{
if(!vis[G[x][i]])
{
cMinID = min(cMinID, postRoot(G[x][i])); //维护minID
}
}
cMinID = min(cMinID, ++tempID);
n2ID[x] = tempID; //维护双向映射的信息
ID2n[tempID] = x; //维护双向映射的信息
minID[tempID] = cMinID; //记录inID
return cMinID;
}
int sumv[100007]={}; //树状数组
void add(int x, int a) //x: id
{
for(; x <= N; x += (x & (-x)) )
{
sumv[x] += a;
}
}
int psum(int x) //x: id
{
int res = 0;
for(; x > 0; x -= (x & (-x)) )
{
res += sumv[x];
}
return res;
}
bool exist[100007]; //[num]
int main()
{
scanf("%d",&N);
for(int i = 0; i < N-1; i++)
{
int a,b;
scanf("%d%d", &a, &b);
AddEdge(a,b);
}
postRoot(1); //后根遍历
for(int i = 1; i <= N; i++)
{
exist[i] = true; //初始时各处都有苹果
add(i, 1); //初始时各处都有苹果
}
scanf("%d",&M);
for(int i = 0; i < M; i++)
{
char cmd;
int n;
while(scanf("%c", &cmd), cmd == '\n'); //滤掉回车
scanf("%d", &n);
if(cmd == 'Q')
{
int nID = n2ID[n];
int ans = psum(nID) - psum(minID[nID]-1); //求[minID[nID], nID]区间的和
printf("%d\n", ans);
}
else if(cmd == 'C')
{
if(exist[n]) //已有苹果,则吃掉并维护信息
{
exist[n] = false;
add(n2ID[n], -1);
}
else //木有苹果,则长出并维护信息
{
exist[n] = true;
add(n2ID[n], 1);
}
}
}
return 0;
}