Luogu P6623 [省选联考 2020 A 卷] 树|Trie

Luogu P6623 [省选联考 2020 A 卷] 树|01Trie

题目链接

题目大意:

\[1\leq n,v_i \leq 525010,1\leq p_i\leq n \]

思路:

这题有写树上差分的,桶+Dsu on tree的,这里来一种01trie的方法。

考虑使用dfs,在每个点维护一个trie,对于每个节点,先与子节点的trie合并,然后按位统计该位\(0/1\)个数,并在向上更新答案时进行\(+1\)的操作,得出答案。

二进制中的加\(1\),就是从末位开始,若该位为\(1\),则变\(0\),同时往下一为加\(1\),直到该位为\(0\),则变\(1\),因此在树中不断交换\(0\)子树和\(1\)子树,并向\(0\)子树递归即可。显而易见的,建树要从最低位开始建。

01trie中的合并与线段树合并相差不大,稍作修改即可。

上代码:

#include<bits/stdc++.h>
using namespace std;
int w[530000][22][2],g,tmp,rot[530000];
//在实现中,用w数组存每个节点每位的0/1个数
int cc,to[530000],net[530000],fr[530000],v[530000],n,fa;
long long ans;
struct trie
{
	int f0,f1,s;
}t[30052210];//需要空间较大,务必注意
void addedge(int u,int v)
{
	cc++;
	to[cc]=v;net[cc]=fr[u];fr[u]=cc;
}
void add(int x,int y,int v)
{
	t[x].s++;
	if (y==21) return ;
	if (v&1)
	{
		if (!t[x].f1) g++;t[x].f1=g;
		add(t[x].f1,y+1,v>>1);w[tmp][y+1][1]++;
	}
	else
	{
		if (!t[x].f0) g++;t[x].f0=g;
		add(t[x].f0,y+1,v>>1);w[tmp][y+1][0]++;
	}
}
void hebing(int x,int y)
{
	t[x].s+=t[y].s;
	if ((!x)||(!y)) return;
	if (t[y].f0&&!t[x].f0)
		t[x].f0=t[y].f0;
	else hebing(t[x].f0,t[y].f0); 
	if (t[y].f1&&!t[x].f1)
	    t[x].f1=t[y].f1;
	else hebing(t[x].f1,t[y].f1);
} //01trie合并
void plusone(int x,int y)
{
	if (!x) return ;
	w[tmp][y+1][0]-=t[t[x].f0].s;
	w[tmp][y+1][1]-=t[t[x].f1].s;
	swap(t[x].f0,t[x].f1);
	w[tmp][y+1][0]+=t[t[x].f0].s;
	w[tmp][y+1][1]+=t[t[x].f1].s;	
	plusone(t[x].f0,y+1);
}//加一
void dfs(int x)
{	
	int s=0;
	rot[x]=++g;
	tmp=x;add(rot[x],0,v[x]);
	for (int i=fr[x];i;i=net[i])
	{
		dfs(to[i]);
		hebing(rot[x],rot[to[i]]);
		for (int j=1;j<=21;j++)
		{		
			w[x][j][0]+=w[to[i]][j][0];
			w[x][j][1]+=w[to[i]][j][1];
		}
	}
	for (int i=1;i<=21;i++)
	{
		if (w[x][i][1]%2) s+=1<<(i-1);
	}
	ans+=s;
	tmp=x;plusone(rot[x],0);
}
int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
	{
		scanf("%d",&v[i]);
	}
	for (int i=2;i<=n;i++)
	{
		scanf("%d",&fa);
		addedge(fa,i);
	}
	dfs(1);
	cout<<ans<<endl;
	return 0;
}
posted @ 2020-10-18 00:00  fmj_123  阅读(166)  评论(0)    收藏  举报