题目大意:有一棵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;
}