【树上启发式合并】2019ICPC西安邀请赛:J. And And And(对边)

【树上启发式合并】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);
}


  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值