2019西安邀请赛 J And And And (树上启发式合并+组合数)

传送门

题意:
给定一个带边权的有根树,
定义E(U , V)表示u到v简单路径上经过节点构成的点集,
X(U , V)表示u到v简单路径上的边权异或的结果。
然后让我们求这个东西:

在这里插入图片描述

这个式子看上去特别复杂,实际上也确实很复杂
但其实就是,如果(u,v)的简单路径上存在一个点对i,j满足X(i,j)==0,那么(u,v)情况下的(i,j)可以给答案贡献1 。

首先X(i,j)==0可以转化成X(1,i) xor X(1,j) == 0 ,
当我们找到一个点对i,j满足X(1,i) xor X(1,j) == 0 ,有两种情况:
①如果i,j没有祖先关系,那么可以给答案贡献size[i] * size[j],很好理解,就是i j两棵子树里面分别选一个

②如果i,j存在祖先关系,不妨假设i是j的祖先,那么可以给答案贡献size[j]*(n-size[f]) ,f是j所在子树的根节点。

然后就可以枚举LCA作为根节点,用树上启发式了。

答案来源有两个:
1、LCA只会贡献其到某棵子树节点的答案,即②,由于我们dfs的顺序是重儿子答案统计完后,回到根节点,再重新一颗一颗地处理轻儿子子树,所以LCA(也就是根节点)到重子树的答案要先计算好,LCA到轻儿子子树的答案要在轻儿子子树统计的过程中完成。并且要在LCA子树全部处理完后,在把LCA这个点的信息更新上去,不然可能会多贡献出①的答案。

2、 LCA不同子树之间路径的答案,即① 。对于每颗轻子树,先统计答案,再更新信息。

#include<bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define ull unsigned long long
#define ll long long
#define pii pair<int, int>
const int maxn = 1e5 + 10;
const ll mod = 1e9 + 7;
const ll inf = (ll)4e16+5;
const int INF = 1e9 + 7;
const double pi = acos(-1.0);
ll inv(ll b){
    
    while(b==1)return 1;return(mod-mod/b)*inv(mod%b)%mod;}
inline ll read()
{
    
    
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){
    
    while(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){
    
    x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
vector<pair<int,ll> > g[maxn];
int n;
int fa[maxn],sz[maxn],in[maxn],clk,pos[maxn],son[maxn];
ll dis[maxn];
ll ans=0;
unordered_map<ll,ll> mp;
//dis[u]^dis[v]=0
void dfs1(int rt)
{
    
    
    sz[rt]=1;
    in[rt]=++clk,pos[clk]=rt;
    for(auto i:g[rt])
    {
    
    
        int u=i.first;
        dis[u]=dis[rt]^i.second;
        dfs1(u);
        sz[rt]+=sz[u];
        if(sz[u] > sz[son[rt]]) son[rt]=u;
    }
    return ;
}
int lca;
inline void add(int rt)
{
    
    
    for(int i=in[rt];i<in[rt]+sz[rt];i++)
    {
    
    
        int u=pos[i];
        if(dis[u] == dis[lca]) //lca到轻子树中 一条链的情况
        {
    
       
            ans=(ans+1ll*sz[u]*(n-sz[rt])%mod) % mod;
        }
        ans=(ans+1ll*sz[u]*mp[dis[u]]%mod)%mod;
    }
    return ;
}
inline void upd(int rt)
{
    
    
    for(int i=in[rt];i<in[rt]+sz[rt];i++)
    {
    
    
        int u=pos[i];
        mp[dis[u]]+=sz[u];
    }
    return ;
}
void dfs2(int rt,bool save) 
{
    
    
    for(auto i:g[rt])
    {
    
    
        int u=i.first;
        if(u==son[rt]) continue;
        dfs2(u,0);
    }
    if(son[rt]) 
    {
    
    
        dfs2(son[rt],1);
        //rt->u  dis(u)^dis(rt)==0
        ans=(ans+1ll*(n-sz[son[rt]])*mp[dis[rt]] % mod) % mod;//lca到重子树中 在同一条链的情况
    }
    lca=rt;
    for(auto t:g[rt])
    {
    
    
        int u=t.first;
        if(u==son[rt]) continue;
        add(u);//先统计答案
        upd(u);//再更新map
    }
    mp[dis[rt]]+=sz[rt];//这个也是最后加 因为我们前面考虑过LCA的信息了
    if(!save) mp.clear();
    return ;
}
int main()
{
    
    
    scanf("%d",&n);
    for(int i=2;i<=n;i++)
    {
    
    
        fa[i]=read();
        ll w=read();
        g[fa[i]].push_back({
    
    i,w});
    }
    dfs1(1);
    dfs2(1,1);
    printf("%lld\n",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_46030630/article/details/121363929