2019ICPC西安邀请赛 J.And And And
题意
给你一颗带有边权的树,问所有简单路径包含的异或值为0的简单路径的总条数。
做法
首先这道题正向做不好做,我们要考虑反向计算贡献,也就是计算每条异或值为0的路径被计算了多少次。
可以发现每个异或值为0的路径u->v,只需要保证u到根节点的异或值等于v到跟节点的异或值即可。
于是我们把边权转换为点权,定义sz[i]表示以i为根的子树大小。
对于不互为祖先的u,v,我们发现这个路径对答案的贡献就是sz[u]*sz[v]。
因为u子树内每一个节点到v子树内每一个节点都会经过这条路径。
之后我们发现对于u是v祖先或者v是u祖先的情况,这个贡献是不对的。
所以我们首先减去错误的贡献,用一个map统计当前链上每种权值的sz之和即可。
之后我们需要加上正确的贡献,对于u是v祖先的情况,我们发现贡献就是sz[v]×(n-u通往v方向的子树大小),这个画个图就可以理解。
于是我们用另一个map维护当前链上的这个信息即可。
这里用unordered_map,时间复杂度为O(n)。
代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<map>
#include<vector>
#include<unordered_map>
using namespace std;
typedef long long ll;
typedef pair <int,ll> pil;
const int maxn = 1e5+5;
const int Mod=1000000007;
#define Se second
#define Fi first
#define pb push_back
unordered_map<ll,ll>mp;
unordered_map<ll,ll>mp2;
unordered_map<ll,ll>sum;
ll sz[maxn],val[maxn],ans,w;
vector<pil> G[maxn];
int n,fa;
void dfs(int rt,ll XOR)
{
val[rt]=XOR,sz[rt]=1;
for(int i=0;i<G[rt].size();i++)
{
dfs(G[rt][i].Fi,XOR^G[rt][i].Se);
sz[rt]=sz[rt]+sz[G[rt][i].Fi];
}
}
void dfs2(int rt)
{
ans=(ans-1LL*sz[rt]*mp[val[rt]]%Mod+1LL*sz[rt]*mp2[val[rt]]%Mod+Mod)%Mod;
mp[val[rt]]=(mp[val[rt]]+sz[rt])%Mod;
for(int i=0;i<G[rt].size();i++)
{
int to=G[rt][i].Fi;
mp2[val[rt]]=(mp2[val[rt]]+n-sz[to]+Mod)%Mod;
dfs2(to);
mp2[val[rt]]=(mp2[val[rt]]-n+sz[to]+Mod)%Mod;
}
mp[val[rt]]=(mp[val[rt]]-sz[rt]+Mod)%Mod;
}
int main()
{
scanf("%d",&n);
for(int i=2;i<=n;i++)
{
scanf("%d%lld",&fa,&w);
G[fa].pb(pil(i,w));
}
dfs(1,0);
dfs2(1);
for(int i=1;i<=n;i++)
{
ans=(ans+1LL*sz[i]*sum[val[i]])%Mod;
sum[val[i]]=(sum[val[i]]+sz[i])%Mod;
}
printf("%lld\n",ans);
return 0;
}