【树上启发式合并】2019ICPC西安邀请赛:J. And And And(对边)
题意:
一颗带边权的树,统计所有的点集合中好点对 ( i , j ) (i,j) (i,j)的个数,好点对 ( i , j ) (i,j) (i,j)表示点 i i i到 j j j点所有的路径异或和等于 0 0 0。
思路:
显而易见: 以
1
1
1为根节点,根据异或和的性质,若两个点
i
,
j
i,j
i,j到
1
1
1的异或和相等,则
(
i
,
j
)
(i,j)
(i,j)为好点对。直接树上启发式,具体细节见代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int maxn = 1e5+5;
const int mod = 1e9+7;
unordered_map<ll,ll>tmp;
ll ans,dis[maxn];
int n,size[maxn],dfn[maxn],id[maxn],tim,son[maxn];
vector<int>mp[maxn];
vector<ll>w[maxn];
void dfs1(int x)
{
size[x] = 1;
dfn[x] = ++tim;
id[tim] = x;
for(int i=0;i<mp[x].size();i++){
int y=mp[x][i];
dis[y] = dis[x]^w[x][i];
dfs1(y);
size[x] += size[y];
if(size[y] > size[son[x]])son[x] = y;
}
}
void dfs(int x,bool op){
for(int i=0;i<mp[x].size();i++){
int y=mp[x][i];
if(y!=son[x])dfs(y,0);
}
if(son[x])dfs(son[x],1);
//if(son[now])
ans = (ans+(n-size[son[x]])*tmp[dis[x]]%mod)%mod;//遍历完重儿子后,再更新当前
for(int i=0;i<mp[x].size();i++){
int v=mp[x][i];
if(v==son[x])continue;
for(int j = dfn[v];j <= dfn[v]+size[v]-1;++j){//遍历一条轻儿子链
int y = id[j];
ans =(ans+ 1ll*size[y]*tmp[dis[y]]%mod)%mod;//
if(dis[y] == dis[x])ans =(ans+ 1ll*(n-size[v])*size[y]%mod)%mod;//注意当前根节点是v
}
for(int j = dfn[v];j <= dfn[v]+size[v]-1;++j){//遍历完后才更新mp
int y = id[j];
tmp[dis[y]] += size[y];
}
}
tmp[dis[x]] += size[x];//当前
if(!op)tmp.clear();
}
int main(){
scanf("%d",&n);
for(int i=2,x;i<=n;i++){
ll z;
scanf("%d%lld",&x,&z);
mp[x].push_back(i);
w[x].push_back(z);
}
dfs1(1);
dfs(1,0);
printf("%lld\n",ans);
}