题目大意:
给定一个
n
n
n 个节点的树,求:
∑
i
=
1
n
∑
j
=
i
+
1
n
[
a
i
⊕
a
j
=
a
l
c
a
(
i
,
j
)
]
(
i
⊕
j
)
\sum_{i=1}^n \sum_{j=i+1}^n [a_i ⊕ a_j = a_{lca(i,j)}](i ⊕ j)
i=1∑nj=i+1∑n[ai⊕aj=alca(i,j)](i⊕j)
题目分析:
我们发现在一条链上的两个点
u
,
v
u,v
u,v (
d
e
p
u
<
d
e
p
v
dep_u<dep_v
depu<depv),有
l
c
a
(
u
,
v
)
=
u
lca(u,v)=u
lca(u,v)=u 此时应有
a
u
⊕
a
v
≠
a
u
a_u ⊕ a_v \ne a_u
au⊕av=au 而题目保证
1
≤
a
i
≤
1
e
6
1\le a_i \le 1e6
1≤ai≤1e6 所以与数据范围矛盾,所以一条链上的两个点
u
,
v
u,v
u,v 不可能对答案产生贡献
那么此时的问题就变成了一个离线书上问题:求出每一个子树的
l
c
a
lca
lca 为子树根的子节点们对答案的贡献总和。
离线处理子树问题的一个处理利器就是树上启发式合并
答案是点的下标的异或和,考虑将数进行二进制拆分,逐位比较模拟异或操作,如果相同就加上贡献即可
具体细节见代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
int read()
{
int res = 0,flag = 1;
char ch = getchar();
while(ch<'0' || ch>'9')
{
if(ch == '-') flag = -1;
ch = getchar();
}
while(ch>='0' && ch<='9')
{
res = (res<<3)+(res<<1)+(ch^48);//res*10+ch-'0';
ch = getchar();
}
return res*flag;
}
const int maxn = 1e6+5;
const int mod = 1e9+7;
const double pi = acos(-1);
const double eps = 1e-8;
vector<int> nod[maxn],tmp;
int a[maxn],siz[maxn],son[maxn];
int cnt[(1<<20)+5][25][2];
ll ans;
bool vis[maxn];
void update(int id,int val) //更新下标数组
{
for(int i = 0;i < 20;i++)
cnt[a[id]][i][(id>>i)&1] += val;
}
ll query(int id,int num) //查询价值
{
ll res = 0;
for(int i = 0;i < 20;i++)
res += cnt[num][i][!((id>>i)&1)]*(1<<i);
return res;
}
void calc(int u,int fa,int lca) //计算子树贡献
{
tmp.push_back(u);
ans += query(u,a[u]^a[lca]);
for(auto to:nod[u])
{
if(to == fa || vis[to])
continue;
calc(to,u,lca);
}
}
void del(int u,int fa) //删除贡献
{
update(u,-1);
for(auto to:nod[u])
{
if(to == fa || vis[to])
continue;
del(to,u);
}
}
void dfs1(int u,int fa) //轻重链处理
{
siz[u] = 1;
for(auto to:nod[u])
{
if(to == fa) continue;
dfs1(to,u);
siz[u] += siz[to];
if(siz[to] > siz[son[u]])
son[u] = to;
}
}
void dfs2(int u,int fa,int keep)
{
for(auto to:nod[u])
{
if(to == fa || to == son[u])
continue;
dfs2(to,u,0);
}
if(son[u])
{
dfs2(son[u],u,1);
vis[son[u]] = true;
}
update(u,1);
for(auto to:nod[u])
{
if(to == fa || vis[to])
continue;
calc(to,u,u);
for(auto it:tmp)
update(it,1);
tmp.clear();
}
if(son[u])
vis[son[u]] = false;
if(!keep) //删除轻链贡献
del(u,fa);
}
int main()
{
int n = read();
for(int i = 1;i <= n;i++)
a[i] = read();
for(int i = 1;i < n;i++)
{
int u = read(),v = read();
nod[u].push_back(v);
nod[v].push_back(u);
}
dfs1(1,0);
dfs2(1,0,1);
printf("%lld\n",ans);
return 0;
}