#MNS149. 异或(AC代码)

#MNS149. 异或

【题目描述】

一棵 n 个节点的树,第 i 个节点的权值是 a_i

每个节点有一个特殊值,定义为该节点到根节点的路径的权值异或和。

这棵树的权值是每个节点的特殊值之和。

现在你为这棵树确定一个根,使得这棵树的权值最大,并输出这个最大值。

【输入格式】

xor.in中读入数据。

第一行输入一个正整数 n

第二行输入 n 个正整数 a_i

接下来 n-1 行,每行包含两个正整数 u,v,表示第 u 个点和第 v 个点之间连边。

【输出格式】

输出到xor.out中。

输出这棵树的最大权值。

【输入样例 1】

3
1 1 2
1 2
2 3

【输出样例 1】

7

【输入样例 2】

4
2 2 0 3
1 2
1 3
2 4

【输出样例 2】

10

【数据范围及约定】

  • 对于测试点 1\sim 41\le n\le 10,0\le a_i < 2^5

  • 对于测试点 5\sim 81\le n\le 1000,0\le a_i < 2^{10}

  • 对于测试点 7\sim 201\le n\le 100000,0\le a_i < 2^{30}

【样例1解释】

以第 1 个点为根,第 1,2,3 个点的特殊值分别是 1,0,2,整棵树的权值是 1+0+2=3

以第 2 个点为根,第 1,2,3 个点的特殊值分别是 0,1,3,整棵树的权值是 0+1+3=4

以第 3 个点为根,第 1,2,3 个点的特殊值分别是 2,3,2,整棵树的权值是 2+3+2=7


【题解】

  • 暴力

是枚举根节点,通过dfs的方式逐个计算每个点的特殊值并累加得到整棵树的权值,这样的方法是O(n^2)的,可以通过前2档得分,但不足与通过最后一档得分。

  • 优化

容易发现当根节点确定时,每一位的贡献可分开计算。

首先以1为根跑一遍dfs,设f_{i,j}表示第i个点的子树内的特殊值第j位是1的节点的个数。则第j位对答案的贡献是f_{i,j}*2^j

不妨设第1个点的其中一个儿子是第2个点,如果能通过第1个点的f数组计算出以第2个点为根的f数组,则完成了换根的过程。

首先以第1个点为根的整棵树的权值需减去以第1个点为根的第2个点的子树的特殊值,剩下的这部分节点的特殊值,第2个点必须经过第1个点才能得到。当第2个点是根时,以第1个点为根的第2个点的子树的特殊值不受其他节点影响,可以保留下来。

考虑第2个节点子树外的部分的特殊值会变化成什么,容易发现这部分的特殊值多异或了第2个节点的权值,即a_2。因此如果a_2的第j位是1需要将对应位的0,1个数互换。

综上所述,完成了整个换根的过程。计算出了每个点为根时的整棵树权值,选择最大值输出即可。

换根具体细节实现见代码。

【代码】 

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int n;
int a[maxn];
vector<int>g[maxn];
int f[maxn][30];
int siz[maxn];
long long ans=0;
void dfs(int fa,int u,int z)
{
	siz[u]=1;
	int now=(a[u]^z);
	for(int i=0;i<30;i++)
		if((now>>i)&1)
			f[u][i]+=1;
	for(auto v:g[u]){
		if(v==fa)continue;
		dfs(u,v,now);
		for(int i=0;i<30;i++)f[u][i]+=f[v][i];
		siz[u]+=siz[v];
	}
}
void dfs2(int fa,int u,int z)
{
	if(u!=1){
		for(int i=0;i<30;i++){
			int c=0;
			int one=0;
			if(!(((a[fa]^z)>>i)&1))one+=f[fa][i]-f[u][i];
			else one+=f[fa][i]-(siz[u]-f[u][i]);
			if((a[u]>>i)&1)one=n-siz[u]-one;
			c+=one;
			if(!((z>>i)&1))c+=f[u][i];
			else c+=siz[u]-f[u][i];
			f[u][i]=c;
		}
		long long val=0;
		for(int i=0;i<30;i++)val+=(1ll<<i)*f[u][i];
		ans=max(ans,val);
	}
	for(auto v:g[u]){
		if(v==fa)continue;
		dfs2(u,v,z^a[u]);
	}
}
int main() 
{
	//freopen("xor.in","r",stdin);
	//freopen("xor.out","w",stdout);
	std::ios::sync_with_stdio(false),cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=n-1;i++){
		int u,v;
		cin>>u>>v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	dfs(1,1,0);
	for(int i=0;i<30;i++)ans+=(1ll<<i)*f[1][i];
	dfs2(1,1,0);
	cout<<ans<<endl;
	return 0;
}

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值