题目
n(n<=1e5)个点的树,是一棵点1为根的有根树,
每条边有一边权w(0<=w<=1e18)
简化题意,如果u<v,且树上(u,v)之间的边权异或和为0,
则如果把(u,v)这条边全删掉,计此时u的连通块内的点个数为sz[u],v的连通块内的点个数为sz[v],
则其对答案的贡献为sz[u]*sz[v],
求总的答案,答案对1e9+7取模
思路来源
发现过了一年,这个题自己已经会了
题解
分类讨论,一条链和不是一条链的情况
启发式合并,先不把lca插进去,
考虑轻儿子往上挂的过程,(重子树里的点u,轻子树里的点v)的贡献显然是sz[u]*sz[v],
所以开个unorderedmap,now[xor]记录异或值为xor的sz[u]之和,sz[v]乘上即可,也即sz[v]*now[dis[v]]
比较难统计的是链一端在lca的情形,分两种情况,
此时lca往上的所有点都可以成为sz中的点,
①如果是(重子树里的u,lca),计lca的重儿子为son,则贡献是sz[u]之和*(n-sz[son]),也即now[dis[lca]]*(n-sz[son])
②如果是(轻子树里的u,lca),计lca当前搜到的轻儿子v,则贡献是sz[u]*(n-sz[v])
继承重儿子,扫完一批轻儿子将这批轻儿子插入,扫完儿子之后将lca插入
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,ll> P;
#define fi first
#define se second
#define pb push_back
const int N=1e5+10,INF=0x3f3f3f3f,mod=1e9+7;//998244353
vector<P>E[N];
int n,f;
ll w,ans;
unordered_map<ll,ll>now;
ll dis[N],d[N];
int sz[N];
int st[N],ed[N],dfn[N],tot;
void dfs(int u,int fa){
sz[u]=1;
st[u]=++tot;
dfn[tot]=u;
for(P x:E[u]){
int v=x.fi;ll w=x.se;
if(v!=fa){
dis[v]=dis[u]^w;
d[v]=d[u]+1;
dfs(v,u);
sz[u]+=sz[v];
}
}
ed[u]=tot;
}
void dfs(int u,int fa,bool keep){
int mx=-1,son=-1;
for(P x:E[u]){
int v=x.fi;
if(v!=fa&&sz[v]>mx)
mx=sz[v],son=v;
}
for(P x:E[u]){
int v=x.fi;
if(v!=fa&&v!=son){
dfs(v,u,0);
}
}
if(son!=-1){
dfs(son,u,1);
ans=(ans+1ll*now[dis[u]]*(n-sz[son])%mod)%mod;
}
for(P x:E[u]){
int v=x.fi;ll w=x.se;
if(v!=fa&&v!=son){
for(int i=st[v];i<=ed[v];i++){
int x=dfn[i];
if(now.count(dis[x])){
ans=(ans+1ll*now[dis[x]]*sz[x]%mod)%mod;
}
if(dis[x]==dis[u]){
ans=(ans+1ll*(n-sz[v])*sz[x]%mod)%mod;
}
}
for(int i=st[v];i<=ed[v];i++){
int x=dfn[i];
now[dis[x]]+=sz[x];
}
}
}
now[dis[u]]+=sz[u];
if(keep==0){
now.clear();
}
}
int main(){
scanf("%d",&n);
for(int i=2;i<=n;++i){
scanf("%d%lld",&f,&w);
E[f].pb(P(i,w));//E[i].pb(P(fa,w));
}
dfs(1,-1);
dfs(1,-1,0);
printf("%lld\n",ans);
return 0;
}