LINK

刚好在 g y m gym gym碰到就顺手写了

话说打长春的时候是被队友秒掉的emm

自己写的时候还是有点细节的…

注意虽然这是一颗有根树,但边并不是完全由父亲指向儿子的!!!

需要建双向边,我被这个卡疯了…


∑ i = 1 n ∑ j = i + 1 n [ a i ⊕ a j = a l c a i , j ] ∗ ( i ⊕ j ) \sum\limits_{i=1}^n\sum\limits_{j=i+1}^n[a_i\oplus a_j=a_{lca_{i,j}}]*(i\oplus j) i=1nj=i+1n[aiaj=alcai,j](ij)

发现可以枚举每个点作为 l c a lca lca来计算贡献

那么对于 u u u子树内的点 v v v

若子树中存在另外的节点 k k k满足 a k = a u ⊕ a k a_k=a_u\oplus a_k ak=auak就可以产生贡献

发现快速找到 a u ⊕ a k a_u \oplus a_k auak只需要开个桶即可

v a l [ i ] [ j ] [ 0 / 1 ] val[i][j][0/1] val[i][j][0/1]表示当前子树内 a a a值为 i i i的节点中,二进制第 j j j位有多少个节点是 0 / 1 0/1 0/1

那么这个数字可以用树上启发式合并处理

然后就是枚举子树内每个点,一边计算贡献,一边把子树加入数组

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6+10;
const int N = 1e5+10;
vector<int>vec[N];
int n,a[N],dfn[N],low[N],top[N],id,siz[N],son[N];
void dfs(int u,int fa)
{
	siz[u] = 1; dfn[u] = ++id; top[id] = u;
	for( auto v:vec[u] )
	{
		if( v==fa )	continue;
		dfs(v,u);
		siz[u] += siz[v];
		if( siz[son[u]]<siz[v] )	son[u] = v;
	}
	low[u] = id;
}
int SON,val[maxn][21][2];
long long ans;
//val[i][j][0/1]表示权值为i中二进制第j位为0/1的有多少个 
void update(int ID,int w)
{
	for(int q=0;q<=20;q++)
		val[a[ID]][q][(ID>>q)&1] += w;	
}
void calc(int u,int fa)
{
	update( u,1 );
	for( auto v:vec[u] )
	{
		if( v==SON || v==fa )	continue;
		for(int j=dfn[v];j<=low[v];j++)
		{
			int ID = top[j];
			for(int q=0;q<=20;q++)
				if( (a[u]^a[ID])<=1e6 )
					ans += 1ll*(1<<q)*val[a[u]^a[ID]][q][1^((ID>>q)&1)];
		}
		for(int j=dfn[v];j<=low[v];j++)		update( top[j],1 );
	}
}
void del(int u)
{
	for(int i=dfn[u];i<=low[u];i++)	update( top[i],-1 );	
}
void dsu(int u,int fa,int keep)
{
	for(auto v:vec[u] )
		if( v!=son[u] && v!=fa )	dsu(v,u,0);
	if( son[u] )	dsu( son[u],u,1 ),SON = son[u];
	calc(u,fa); SON = 0;//计算以u为lca的所有答案 
	if( !keep )	del(u);
}
int main()
{
	cin >> n;
	for(int i=1;i<=n;i++)	cin >> a[i];
	for(int i=1;i<n;i++)
	{
		int l,r;scanf("%d%d",&l,&r);
		vec[l].push_back( r ); vec[r].push_back( l );
	}
	dfs(1,1); dsu(1,1,0);
	cout << ans;
}