BZOJ2819 Nim(dfs序+树状数组)

【题解】

题目大意:
给定一棵树,有两种操作:
1. 修改一个节点的权值 
2. 查询两点之间路径上所有点的权值异或值 

运用前缀的思想 
首先xor运算有一个性质:Xor[l,r]=Xor[1,l]^Xor[1,r]
所以,设 Xor[x]为从结点x到根经过的所有结点的权值异或,
则 x到y路径上的点权异或值为:Xor[x] ^ Xor[y] ^ Val[LCA(x,y)]
修改一个点x,会改变以它为根的子树中所有节点的Xor[]值 

求出树上所有节点的dfs序,这个修改就变成了连续区间的修改 
对单点的查询,线段树可以做到 O(logN),
今天又学到了一种新方法:利用前缀和打标记的思想,若将区间[l,r]内的元素全部异或x,相当于在第l位标记x,再在第r+1位标记x,
这样,对于第r位以后的元素,这两个命令互相抵消,查询某个元素的值,只需用树状数组把它之前的命令全部累加起来即可 

另外,关于dfs序,一个结点的r值可以等于它最后一个孩子的r值,不必加1,这样,序列的数目还是N,而不是2*N


RE后省掉了原来的Xor数组,用初始的打标记代替了,然后果断就AC了。。。

【代码】

#include<stdio.h>
#include<stdlib.h>
int a[500005],l[500005],r[500005],f[25][500005],deep[500005],v[1000005],first[500005],next[1000005],c[500005];
int n,e=0,pos=0;
void tj(int x,int y)
{
	v[++e]=y;
	next[e]=first[x];
	first[x]=e;
}
void dfs(int x,int fa)
{
	int i;
	l[x]=++pos;
	f[0][x]=fa;
	deep[x]=deep[fa]+1;
	for(i=first[x];i!=0;i=next[i])
		if(v[i]!=fa) dfs(v[i],x);
	r[x]=pos;
}
int LCA(int x,int y)
{
	int i,j,t;
	if(deep[x]>deep[y])
	{
		t=x;
		x=y;
		y=t;
	}
	while(deep[x]<deep[y])
	{
		for(i=0;deep[x]<=deep[y]-(1<<i);i++);
		y=f[i-1][y];
	}
	while(x!=y)
	{
		for(i=1;f[i][x]!=f[i][y];i++);
		x=f[i-1][x];
		y=f[i-1][y];
	}
	return x;
}
void xg(int p,int i)
{
	for(;i<=n;i+=i&(-i))
		c[i]^=p;
}
int cx(int i)
{
	int ans=0;
	for(;i>0;i-=i&(-i))
		ans^=c[i];
	return ans;
}
int main()
{
	char opt;
	int q,i,j,x,y;
	scanf("%d",&n);
	for(i=1;i<=n;i++)
		scanf("%d",&a[i]);
	for(i=1;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		tj(x,y);
		tj(y,x);
	}
	dfs(1,0);
	for(i=1;i<=20;i++)
		for(j=1;j<=n;j++)
			f[i][j]=f[i-1][f[i-1][j]];
	for(i=1;i<=n;i++)
	{
		xg(a[i],l[i]);
		xg(a[i],r[i]+1);
	}
	scanf("%d",&q);
	for(;q>0;q--)
	{
		scanf("\n%c %d%d",&opt,&x,&y);
		if(opt=='C')
		{
			xg(a[x]^y,l[x]);
			if(r[x]<n) xg(a[x]^y,r[x]+1);
			a[x]=y;
		}
		else
		{
			if( cx(l[x])^cx(l[y])^a[LCA(x,y)] ) printf("Yes\n");
			else printf("No\n");
		}
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值