The 2019 ACM-ICPC China Shannxi Provincial Programming Contest J.And And And(启发式合并)

本文详细解析了一个关于树上边权异或和的问题,涉及树的结构、连通块的计算以及启发式合并策略。题目要求在删除特定边后,根据连通块内的点个数计算对答案的贡献,并对结果取模。通过分类讨论和使用unordered_map数据结构,实现了高效求解。代码中展示了如何遍历树节点并更新贡献值,最终得出答案。
摘要由CSDN通过智能技术生成

题目

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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值