子树查询(POJ3321)

题目大意:
有一棵苹果树,树上有N个叉(编号为1~N,根的编号为1),它们通过分支连接。苹果在叉上生长,两个苹果不会在同一个叉上生长,一个新的苹果可以在空叉上长出来,对苹果有两种操作。
在这里插入图片描述

输入:
第1行一个整数N(N≤100,000),表示树中叉的数量。以下N-1行,每行都包含两个整数,表示叉u和叉v通过分支连接。下一行包含整数M(M≤100,000)表示有M个操作。每行一个操作:
C x表示改变叉上苹果状态(摘下一个苹果,或让长出一个新苹果)。
Q x表示查询x叉上方子树中的苹果数量,包括叉上的苹果。注意:开始时树上长满苹果。

输出:
对每个查询,都单行输出答案。

输入样例:
3
1 2
1 3
3
Q 1
C 2
Q 1

输出样例:
3
2

题解:
本题包含两种操作,一种是点更新,一种是查询以当前节点为根的子树的苹果数量。
对一棵树深度遍历,则记录遍历时当前节点进来和出去时的序号,两个序号之间的节点就是当前节点的子树节点。可以利用DFS序列将子树转换为序列,然后求解区间和。
点更新,求解区间和都可以用树状数组操作(区间和就是两个前缀和的差)。
本题中,摘掉一个苹果或新长出一个苹果,都会使父节点的个数改变,这也是使用树状数组的一个关键。

图解:
输入数据如下:
5
1 3
1 2
3 5
3 4
在这里插入图片描述
在这里插入图片描述

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int maxn=1e5+10;
int N,q;
int c[maxn];
int a[maxn];//a[i]记录每个节点上有无苹果,默认都有 
int L[maxn],R[maxn];
int head[maxn];
int cnt;
int dfn;
struct edge{
    int u,v;
    int next;
}E[2*maxn];

void adde(int u,int v){
    E[++cnt].u=u;
    E[cnt].v=v;
    E[cnt].next=head[u];
    head[u]=cnt;
}

int lowbit(int x){
    return x&(-x);
}

void add(int i,int v){
    for(;i<=N;i+=lowbit(i))
        c[i]+=v;
}

int sum(int i){
    int ans=0;
    for(;i>0;i-=lowbit(i)){
        ans+=c[i];
    }
    return ans;
}

void init(){
    memset(c,0,sizeof(c));
    memset(L,0,sizeof(L));
    memset(R,0,sizeof(R));
    memset(head,0,sizeof(head));
    cnt=0;
    dfn=0;
    for(int i=1;i<=N;i++){
        a[i]=1;
        add(i,1);
    }
    
	//测试用 
	//for(int i=1;i<=N;i++) printf("%d ",c[i]); 
	 
}
//求出树的dfs序,即先序遍历,则一个子树的所有结点对应dfs序上连续的一段
void dfs(int u,int fa){
	L[u]=++dfn;
    for(int i=head[u];i;i=E[i].next){
        int v=E[i].v;
        if(v==fa) continue;
        dfs(v,u);
    }
    R[u]=dfn;
}/*记录每个节点的进入序号和出去序号,就知道子树有几个节点
但是默认每个节点都有一个苹果,默认节点的个数就是苹果的个数。 
但该题节点上可以摘掉苹果,并且频繁改变节点状态,所以采用树状数组来统计 
*/ 
int main(){
    scanf("%d",&N);
    int u,v;
    init();
    for(int i=1;i<N;i++){
        scanf("%d%d",&u,&v);
        adde(u,v);
    }
    dfs(1,1);
  /*printf("测试dfs序列\n"); 
	for(int i=1;i<=N;i++)//测试 
    	printf("%d %d\n",L[i],R[i]);
    printf("\n");*/	
    scanf("%d",&q);
    char op[10];
    for(int i=1;i<=q;i++){
        scanf("%s %d",op,&v);
		/*用接收字符串的方式,避免多余的空格和换行符带来的困扰,
		也可以使用 scanf(" %c%d", &op, &v);用空格屏蔽换行符,第一次见到 */ 
        if(op[0]=='C'){
            if(a[L[v]])
                add(L[v],-1);
            else
                add(L[v],1);
            a[L[v]]^=1;
        }
        else{
            int s1=sum(R[v]);
            int s2=sum(L[v]-1);
            printf("%d\n",s1-s2);
        }
    }
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值