代码短小精悍,理解费劲= =.
总结下思路:
首先将相连的边存起来( insert ):
用数组模拟一个单向链表,因而每一条边存两次;
链表的节点代表一条边,有两个值:苹果树的下一分叉点 & 当前节点上连的上一条边.
一条边的"地址"就是当前分叉点的加入序号(因为是用数组模拟的,也就是下标寻址).***
由于每一个分叉点的编号(除1外)只是一个id,故使用list数组实现id寻址.list值为id分叉点对应的最末加入的一条边的地址. ***这里很绕= =b注意顺序
tree[ list[ id ] ] 就是id分叉点最末加入的一条边啦.
注意由于list是全局变量,自动初始化为全0,也就是第一条边的next为0边.
同一个id分叉点的不同边之间是直接用链表的next寻址的;
不同id分叉点的边是直接借助list数组用id寻址的(只找到最末那条边~).
然后把每一个id打上时间戳( make ):
这里当然也是id寻址.
用一个结构体数组存储每一个id对应的[进入,退出]时间.本代码选择退出增一,进入不变.反之也可~
这个过程要模拟DFS.
简单来说就是;
1 总是找当前节点的下一个节点,直到叶子节点;
2 到达叶子节点后,记录时间戳,入值==出值,出值++;返回上一分叉;
3 检查是否有下一条边(即找下一节点),若有,递归返回步骤1.
若没有,入值=最小入值**,出值=最大出值++**; 返回上一分叉; **由于递归,这里的数值变化不容易看清楚.
调用时 make( 0, 1 ),因此最后执行到步骤3时就会完全退出.
下面就是建立一个满的线段树(初始时每个分叉都有一个苹果).
询问时,根据tab找到此id对应的区间.询问即可.
更改时,单点异或 tab[ id ].r 就行了(因为是对应时间戳的出值).
#include <cstdio>
#include <cstring>
using namespace std;
#define maxn 110000
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
/**
除了知道1为根节点之外其他编号都只能认为是id,数字没有指导意义.可得唯一解.
**/
int min(int a, int b)
{
return a + ((b-a) & ((b-a) >> 31));
}
struct branch{///树枝是单向的
int next,node;
}tree[maxn<<1];//由树枝组成的树
struct Node{
int l,r;
}tab[maxn];//由节点组成的表
int cnt,sum;//树枝计数
///sum是时间戳的游标
int list[maxn],seg[maxn<<2];//seg是线段树,list是..?(下文)
inline void insert(int fro,int to)//插入一条树枝
{
tree[cnt].node = to;///node指向目标节点的id
tree[cnt].next = list[fro];///最初list[0]是0,此后都是fro对应树枝的下标
///而此树枝的next存的是上一次遗留下来的tree下标,由此可以找到上一条由fro引出的树枝!!!
list[fro] = cnt++ ;//list是用id做index的..存的是tree下标
///看next的时候应该猜想"node"存的是节点id,而本身是树枝,所以next应该是指向树枝的指针
///而结构体的next是单向的指向下一节点,所以这里的next也是单向的回溯,只不过是上一个树枝
}
int make(int pre,int now)
{///l是进入时间戳,r是返回时间戳.这里进入不计时间,返回才增
int p = list[now],mi = 2*maxn,t;///list指向最后一条边
while(p){///list数组是初始化为0的,p==0说明已到达最后一条边
t = tree[p].node;///t指向从now出发的下一节点
if(t!=pre)mi = min(make(now,t),mi);///对make总是传入相邻的节点参数
///这里取min是为了一直保留进入时的时间戳,递归结束往回返的时候mi保留着,
///如果找到了下一个儿子,当且仅当每到一个叶子节点,mi会++
p = tree[p].next;///t==pre的话就停止递归,p==tree[p].next==0
///由于之前调用insert的时候,是连续(a,b)(b,a)的,所以,
///如果到达了"叶子树枝"就会出现t==pre的状况!!!
///当然,t!=pre的话递归完了也得走这里往回退
///这个时候就是找下一个树枝,继续...SOGA!!这样就模拟了DFS啊!!!= =b
}
mi = min(mi,sum);///退出的时候,叶子节点左右相等 均为sum.
///到达叶子节点时mi在这里会变大!!!
tab[now].l = mi,tab[now].r = sum++;///tree和list的作用仅限于建时间戳
///到这里,右端点总是++的,叶子节点的话就取mi(上面mi取了INF和sum的最小,是sum)
///不是叶子节点的话,就取实际的sum(儿子全部找完了退出的)
return mi;///返回mi,找儿子的时候mi是一样的!
}
void update(int p,int l,int r,int rt)
{
if(l==r){//XOR 1
seg[rt] ^= 1 ;
return ;
}
int m = (l+r) >> 1;
if(p<=m)update(p,lson);
else update(p,rson);
seg[rt] = seg[rt<<1] + seg[rt<<1|1];
}
int query(int L,int R,int l,int r,int rt)
{
if(L<=l&&R>=r)return seg[rt];
int m = (l+r) >> 1,ret = 0;
if(m>=L)ret += query(L,R,lson);
if(m<R)ret+= query(L,R,rson);
return ret;
}
void build(int l,int r,int rt)
{
if(l==r){
seg[rt] = 1;
return ;
}
int m = (l+r) >> 1;
build(lson),build(rson);
seg[rt] = seg[rt<<1] + seg[rt<<1|1];//PushUp(rt);
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF){
//if(n==0)continue;
int a,b;
sum = cnt = 1;
for(int i=1;i<n;i++){
scanf("%d%d",&a,&b);
insert(a,b);
insert(b,a);
}
make(0,1);//功能应该是把一条条树枝拼接起来,成为一颗苹果树,然后进行DFS遍历,得到时间戳
build(1,n,1);//功能应该是建立线段树,这个线段树是标准求和线段树,本身和苹果树没啥关系
//只是通过苹果树的DFS遍历给苹果树的每个节点打上时间戳,然后在苹果树中找应该查询的区间
//update的时候就是在线段树中单点替换了,因为区间的对应是不会变的
int m,t;
char s[2];
scanf("%d",&m);
while(m--){
scanf("%s%d",s,&t);
if(s[0]=='Q')printf("%d\n",query(tab[t].l,tab[t].r,1,n,1));
//线段树seg是该段的苹果数
//tab中存的是时间戳,tab是以id寻址的..
else {
update(tab[t].r,1,n,1);
}
}
}
return 0;
}