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

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
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;
}

你可能感兴趣的:(图论,dp,算法,c++,图论,数据结构)