[poj 3321]Apple Tree[模拟DFS][时间戳][线段树]

代码短小精悍,理解费劲= =.

总结下思路:

首先将相连的边存起来( 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;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值