ZOJ 3686 A A Simple Tree Problem

开始想了一个错误的算法,对每个节点保存它的子树中的节点数目和其中为一的数目,操作某节点时向上更新其祖先的数据(log N),查询时直接输出。当时忽略了操作同时也需要更新后代的数据,这样的最坏复杂度是N,肯定是不行的。但直接交上去不是WA而是超时,由此猜想其中有类似链表的数据。

这个问题可以用线段树很好地解决,在对原树进行先序遍历的同时标上序号后,可以将任意子树转化为区间。序号介于某节点到它的任意后代间的点必为该节点的后代,在这种排序下即可建立线段树,将对子树的操作转化为对区间的操作。


#include <stdio.h>
#include <memory.h>
#define N 100001
#define M 262144
#define Negate(x) label[x]=!label[x]
#define Comple(a, b) sum[a]=b-sum[a]

int right[N];
int first[N];
int next[N];
int num[N];
int sum[M];
bool label[M];

int a, b, ans;
int n, m, cnt;

void build(int index)
{
	int i = first[index];
	num[index] = ++ cnt;
	right[index] = cnt;
	while(i > 0)
	{
		build(i);
		if(right[i] > right[index])
			right[index] = right[i];
		i = next[i];
	}
}

void update(int cur, int l, int r)
{
	if(a<=l && b>=r)
	{
		Negate(cur);
		Comple(cur, r-l+1);
	}
	else
	{
		int mid = (l+r) >> 1;
		int x = cur<<1, y = x+1;
		if(label[cur] == true)
		{
			Negate(cur);
			Negate(x);
			Negate(y);
			Comple(x, mid-l+1);
			Comple(y, r-mid);
		}
		if(a <= mid)
			update(x, l, mid);
		if(b > mid)
			update(y, mid+1, r);
		sum[cur] = sum[x] + sum[y];
	}
}

void query(int cur, int l, int r)
{
	if (a<=l && b>=r)
		ans += sum[cur];
	else
	{
		int mid = (l+r) >> 1;
		int x = cur<<1, y = x+1;
		if(label[cur] == true)
		{
			Negate(cur);
			Negate(x);
			Negate(y);
			Comple(x, mid-l+1);
			Comple(y, r-mid);
		}
		if(a <= mid)
			query(x, l, mid);
		if(b > mid)
			query(y, mid+1, r);
		sum[cur] = sum[x] + sum[y];
	}
}

int main()
{
	int node;
	while(scanf("%d%d", &n, &m) != EOF)
	{
		memset(first, 0, sizeof(first));
		for(int i=2; i<=n; ++i)
		{
			scanf("%d", &node);
			next[i] = first[node];
			first[node] = i;
		}
		
		cnt = 0;
		build(1);

		memset(label, false, sizeof(label));
		memset(sum, 0, sizeof(sum));
	
		char c[5];
		for(int j=0; j<m; ++j)
		{
			scanf("%s", c);
			scanf("%d", &node);
			a = num[node];
			b = right[node];
			if(c[0] == 'q')
			{
				ans = 0;
				query(1, 1, n);
				printf("%d\n", ans);
			}
			else if(c[0] == 'o')
				update(1, 1, n);
		}
		printf("\n");
	}
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值