POJ 3321(dfs序+树状数组)

题意:一棵苹果树有n个结点,编号从1到n,根结点永远是1。该树有n-1条树枝,每条树枝连接两个结点。已知苹果只会结在树的结点处,而且每个结点最多只能结1个苹果。初始时每个结点处都有1个苹果。树的主人接下来会进行m个操作。操作共两种。C X表示将结点x上的苹果数量改变,原本是1,则现在为0,原本是0,现在是1。Q X表示一次查询。要求输出结点X和其子树上的苹果总数。n和m最大可到100000。


显然可以用树状数组更新求和,但是题目中是树状结构,而一般树状数组的范围是用来求一段区间和的。所以需要考虑的就是如何把树形结构转换为线性【即一段区间】,然后进行普通树状数组的操作即可。


这里转换过程用到了一个小技巧,记录时间戳。方法是,采用dfs对树进行一次遍历,树的每一个结点都有st和ed两个时间戳,分别记录该结点被遍历到的时间戳以及它和它的子树全部遍历完后的时间戳。举一个例子来说明

 然后这就化成了一段区间,下来就可以进行区间求和和更新维护的操作了。


求和:if(ch=='Q') printf("%d\n",getsum(end[num])-getsum(start[num]-1));

然后还有就是修改点权,1变0,0变1.


代码如下:

/****************************************
* author:crazy_石头
* pro:POJ 3321
* date:2014/04/17
* algorithm: 树状数组&&线段树+dfs序 
* 思路:把树状结构dfs下,转化成为区间形式,
* 更新的时候直接对区间更新即可; 
*****************************************/ 
#include 
   
   
    
    
#include 
    
    
     
     
#include 
     
     
      
      
#include 
      
      
       
       
#include 
       
       
         #include 
        
          #include 
         
           #include 
          
            #include 
           
             using namespace std; #define INF INT_MAX #define eps 1e-8 #define A system("pause") #define rep(i,h,n) for(int i=(h);i<=(n);i++) #define ms(a,b) memset((a),(b),sizeof(a)) #define lson l,mid,rt<<1 #define rson mid+1,r,rt<<1|1 #define mod 1e9+7 #define LL long long const int maxn=100000+5; struct edge { int to,next; }e[maxn<<2]; int head[maxn],cnt,vis[maxn],tot,C[maxn];//tot记录时间戳; int start[maxn],end[maxn];//分别记录起始与结束时间戳; inline void addedge(int u,int v) { e[cnt].to=v; e[cnt].next=head[u]; head[u]=cnt++; } inline void add(int u,int v) { addedge(u,v); addedge(v,u); } inline void dfs(int u) { vis[u]=1; start[u]=++tot;//记录开始的时间戳; for(int i=head[u];~i;i=e[i].next) { int v=e[i].to; if(!vis[v]) dfs(v); } end[u]=tot;//记录结束时间戳; } inline int lowbit(int x) { return x&(-x); } inline void update(int x,int d) { while(x<=maxn) { C[x]+=d; x+=lowbit(x); } } inline int getsum(int x) { int ret=0; while(x>0) { ret+=C[x]; x-=lowbit(x); } return ret; } inline void init() { cnt=0; ms(head,-1); } int main() { int n; scanf("%d",&n); init(); tot=0;//时间戳; rep(i,1,n-1) { int u,v; scanf("%d%d",&u,&v); add(u,v); } ms(vis,0); ms(C,0); dfs(1);//dfs序先处理出来,把时间戳存好; //printf("时间戳:\n"); //rep(i,1,n) printf("%d %d\n",start[i],end[i]); rep(i,1,n) update(i,1); int m; scanf("%d",&m); while(m--) { int num; char ch; getchar(); scanf("%c%d",&ch,&num); if(ch=='Q') printf("%d\n",getsum(end[num])-getsum(start[num]-1)); else { if(getsum(start[num])-getsum(start[num]-1)==1) update(start[num],-1); else update(start[num],1); } } //A; return 0; } 
            
           
          
         
       
      
      
     
     
    
    
   
   

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值