D.Tree XOR Codeforces Round 899 (Div. 2)

39 篇文章 0 订阅

Problem - D - Codeforces

题目大意:有一棵n个点的数,每个点i有点权a[i],每次操作可以选择一个以x为根的子树,和一个数y,使x的子树上的所有点异或上y,费用为x的子树大小siz[x]*y,要求使用最小的费用令所有点的点权相同,求以每个点为根时的最小费用。

1<=n<=2e5;0<=a[i]<=1048576

思路:典型的换根dp,首先令1为根求最小费用。

首先要考虑要将所有点变成什么数费用最小,如果只有两个点,那么很显然是把这两个点都修改成与其中一个数相同最优,无论选哪个数,都是另一个数异或他们的异或值,所以对于树上的两个相邻点,也是将它们修改成同一个值更好,那么不妨把所有点都修改成根节点的值,又因为如果两个数都异或了同一个数,它们的异或中有两次重复的这个数,异或值是不会变的,所以要把每个数都变成与根节点相同,每个点的贡献也就是它的子树大小乘以父节点和它的异或,以1为根跑一次dfs就可以求出当前最小费用ans。

然后考虑换根时,费用如何转移,当我们将根从u转移到它的相邻节点v时,首先减去应该舍去的贡献,也就是(a[u]^a[v])*v原来的子树大小siz[v],然后加上新产生的贡献(a[u]^a[v])*v变成根后新产生的子树大小(n-siz[v]),这样就得到了v相对于u的变化量mod,ans[v]就等于ans[u]+mod。

//#include<__msvc_all_public_headers.hpp>
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD = 1e9 + 7;
const int N = 2e5 + 5;
int head[N];
struct Edge
{
	int v, next;
}e[N * 2];
ll n;
ll a[N];
int siz[N];
int cnt = 0;
ll ans[N];
void init()
{
	cnt = 0;
	for (int i = 1; i <= n; i++)
	{
		siz[i] = 1;
		head[i] = -1;
		ans[i] = 0;
	}
}
void addedge(int u, int v)
{
	e[++cnt].v = v;
	e[cnt].next = head[u];
	head[u] = cnt;
}
void dfs1(int u, int fa)
{//求以1为根的答案
	for (int i = head[u]; ~i; i = e[i].next)
	{
		int v = e[i].v;
		if (v == fa)
		{
			continue;
		}
		dfs1(v, u);
		siz[u] += siz[v];//求子树大小
		ans[1] += siz[v] * (a[u] ^ a[v]);
	}
}
void dfs2(int u, int fa)
{//求换根后的答案
	for (int i = head[u]; ~i; i = e[i].next)
	{
		int v = e[i].v;
		if (v == fa)
		{
			continue;
		}
		ans[v] = ans[u] + (n - siz[v] * 2) * (a[u] ^ a[v]);//减去原子树的贡献,加上新子树的贡献
		dfs2(v, u);
	}
}
void solve()
{
	cin >> n;
	init();
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
	}
	for (int i = 1; i < n; i++)
	{
		int u, v;
		cin >> u >> v;
		addedge(u, v);
		addedge(v, u);
	}
	dfs1(1, 0);
	dfs2(1, 0);
	for (int i = 1; i <= n; i++)
	{
		cout << ans[i] << " ";
	}
	cout << endl;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int t;
	cin >> t;
	while (t--)
	{
		solve();
	}
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

timidcatt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值