2020牛客暑期多校训练营(第五场)B Graph —— boruvka最小生成树+字典树

This way

题意:

现在有一棵树,每条边都有一个权值,现在你每次可以做一个操作:加入一条边或者删除一条边。最终使得所有边的权值和最小。
在加入删除的时候要满足以下两条:
1.这张图必须联通
2.每个环上的边的异或和为0

题解:

我在做的时候已经用字典树去做了,但是没有想到用Boruva生成树,在比赛期间没做出来…该死我好菜。这道题好像和CF888G非常相似,代码也基本没变。
首先介绍一下boruvka,它一般用于边由两个点的值通过一些操作之后得到的图中求最小生成树。
做法就是每次找到两个集合,枚举较小的边集所在的集合(和2020牛客三的G题很像),然后找到最小的边,将两个集合连起来,并且答案加上这个值。
然后这种1e5范围的话好像都是需要用什么数据结构优化的,比如说这道题就需要用字典树优化,因为你没办法去枚举所有的连边,而是直接 O ( 30 ) O(30) O(30)的时间去找异或最小值。
那么我们先构建字典树,然后遍历这个字典树,在回溯的时候做。因为异或最小一定是当前点的左右子树的点。那么就枚举较小的数量的子树的所有值,然后在字典树中找到最小的异或值。我们不能从根开始重新找一遍,因为这会找到相同子树的值(或者你回溯到的位置就将这个值在字典树上删掉或许是可以的,但是比较麻烦),并且另一个子树一定是最近的值,所以直接find另一个子树即可。

#include
using namespace std;
#define pa pair
#define ll long long
const int N=1e5+5;
int a[N],cnt,fa[N];
vector<pa>son[N];
vector<int>vec[N];
void dfs(int x,int fa,int v){
    for(auto ne:son[x]){
        if(ne.first==fa)continue;
        a[ne.first]=v^ne.second;
        dfs(ne.first,x,v^ne.second);
    }
}
int finds(int x){return x==fa[x]?fa[x]:fa[x]=finds(fa[x]);}
int nex[N*33][2],pos[N*33],n,tot=1;
void build(){
    sort(a+1,a+n+1);
    for(int i=1;i<=n;i++){
        if(i!=1&&a[i]==a[i-1])continue;
        int now=1;
        for(int j=30;~j;j--){
            int v=((1<<j)&a[i])?1:0;
            if(!nex[now][v])nex[now][v]=++tot;
            now=nex[now][v];
        }
        pos[now]=i,vec[i].push_back(i);
    }
}
int v;
int finds(int x,int rt,int dep){
    int now=rt,s=1<<dep;
    for(int i=dep-1;~i;i--){
        int v=((1<<i)&x)?1:0;
        if(!nex[now][v])
            now=nex[now][v^1],s|=1<<i;
        else now=nex[now][v];
    }
    return s;
}
ll ans;
void dfs(int root,int dep){
    if(!root||!dep)return ;
    int ls=nex[root][0],rs=nex[root][1];
    dfs(ls,dep-1),dfs(rs,dep-1);
    if(!ls||!rs){
        pos[root]=pos[ls+rs];
        return ;
    }
    int lp=pos[ls],rp=pos[rs];
    if(vec[lp].size()>vec[rp].size())
        swap(lp,rp),swap(ls,rs);
    int mi=2e9;
    for(int i:vec[lp]){
        int s=finds(a[i],rs,dep-1);
        mi=min(mi,s);
        vec[rp].push_back(i);
    }
    vec[lp].clear();
    pos[root]=pos[rs];
    ans+=mi;
}
int main()
{
    int x,y,v;
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        scanf("%d%d%d",&x,&y,&v);
        x++,y++;
        son[x].push_back({y,v});
        son[y].push_back({x,v});
    }
    dfs(1,-1,0);
    build();
    dfs(1,31);
    printf("%lld\n",ans);
    return 0;
}
/*
4
0 1 3
0 2 5
0 3 6

6
0 1 1
1 2 4
1 3 3
0 4 5
0 5 2
*/

你可能感兴趣的:(想法,最小生成树,字典树)