boruvka算法学习笔记

在这里插入图片描述
大概就是:初始每个点都是独立的集合,每次merge过程从所有独立集合出发,找到一条权值最小(权值相同则编号)最小的连向其它集合的边,然后合并集合。显然每次都会使得集合数减少一半,所以合并次数是log级别的。
在这里插入图片描述
例题:
luogu3366mst模板题
直接模拟算法过程即可。。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
#define fi first
#define se second
#define pb push_back
#define wzh(x) cerr<<#x<<'='<<x<<endl;
int n,m;
int s[N],t[N],w[N];
struct Boruvka{
  int f[N],use[N],best[N];
  void init(){
    for(int i=1;i<=n;i++)f[i]=i,best[i]=0;
    for(int i=1;i<=m;i++)use[i]=0;
  }
  int find(int x){
    return f[x]==x?x:(f[x]=find(f[x]));
  }
  int better(int x,int y){
    if(!y)return 1;
    if(w[x]<w[y])return 1;
    return x<y;
  }
  LL get(){
    int merge=0;
    LL sum=0;
    while(merge!=n-1){
      for(int i=1;i<=m;i++){
        if(use[i])continue;
        int x=find(s[i]),y=find(t[i]);
        if(x==y)continue;
        if(better(i,best[x]))best[x]=i;
        if(better(i,best[y]))best[y]=i;
      }
      for(int i=1;i<=n;i++){
        if(best[i]){
          int x=find(s[best[i]]),y=find(t[best[i]]);
          if(x!=y){
            use[best[i]]=1;
            f[x]=y;
            merge++;
            sum+=w[best[i]];
          }
          best[i]=0;
        }
      }
    }
    return sum;
  }
}g;
int main() {
  ios::sync_with_stdio(false);
  cin>>n>>m;
  for(int i=1;i<=m;i++)cin>>s[i]>>t[i]>>w[i];
  g.init();
  cout<<g.get()<<'\n';
  return 0;
}

cf888G

把所有值建一个01字典树,然后我们贪心的考虑生成树的过程,因为跟二进制有关系,所以从大到小贪心是合法的。我们从字典树的一个节点考虑,节点的左右儿子必然不同为一个集合。而一个儿子节点代表的集合连出去的最小权值边,必然是跟另一个儿子节点连才是最优的(二进制异或的性质)。那每次递归处理左右儿子,然后再合并左右儿子代表的集合就可以了。合并左右儿子的时候运用启发式合并的思想,枚举size小的一边,在另一个儿子节点代表子树上查异或最小的值。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 6e6 + 10;
#define fi first
#define se second
#define pb push_back
#define wzh(x) cerr<<#x<<'='<<x<<endl;
int n,a[N];
vector<int>v[N];
struct trie{
  int cnt,t[N][2],ta[N];
  void insert(int z,int x,int y){
    v[z].pb(y);
    if(x<0){
      return ;
    }
    bool st=1<<x&y;
    if(!t[z][st])t[z][st]=++cnt;
    insert(t[z][st],x-1,y);
  }
  int query(int z,int x,int y){
    if(x<0)return 0;
    bool st=1<<x&y;
    if(t[z][st]) {
      return query(t[z][st], x - 1, y);
    } else {
      return query(t[z][st ^ 1], x - 1, y) + (1 << x);
    }
  }
}g;
LL gao(int o,int q){
  if(v[o].size()<=1)return 0;
  LL ans=0;
  for(int i=0;i<2;i++){
    if(g.t[o][i]){
      ans+=gao(g.t[o][i],q-1);
    }
  }
  if(!g.t[o][0]||!g.t[o][1])return ans;
  int x=g.t[o][0],y=g.t[o][1];
  if(v[x].size()>v[y].size())swap(x,y);
  LL cm=1e18;
  for(auto k:v[x]){
    cm=min(cm,1ll*g.query(y,q-1,k));
  }
  ans+=cm+(1<<q);
  return ans;
}
int main() {
  ios::sync_with_stdio(false);
  cin>>n;g.cnt=1;
  for(int i=1;i<=n;i++)cin>>a[i];
  //字典树上,两个不同子树必然属于不同的联通快,需要用一个最小权值的边连一起,可以用启发式合并的
  //思想,用小的到大的里面枚举。。。
  for(int i=1;i<=n;i++)g.insert(1,30,a[i]);
  cout<<gao(1,30)<<'\n';
  return 0;
}
//200000*30 

2020牛客多校5B

因为题目要求每时每刻必须联通,且环上异或为0,那么任意两点的连起来的边权都是固定的。
(x,y)的边权=(1-x)的值^(1-y)的值,把每个点z的值 都变成(1-z)路径上边权的异或和。
然后就跟上题一模一样了。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 3e6 + 10;
#define fi first
#define se second
#define pb push_back
#define wzh(x) cerr<<#x<<'='<<x<<endl;
int n,a[N];
vector<int>v[N];
struct trie{
  int cnt,t[N][2];
  void insert(int z,int x,int y){
    v[z].pb(y);
    if(x<0){
      return ;
    }
    bool st=1<<x&y;
    if(!t[z][st])t[z][st]=++cnt;
    insert(t[z][st],x-1,y);
  }
  int query(int z,int x,int y){
    if(x<0)return 0;
    bool st=1<<x&y;
    if(t[z][st]) {
      return query(t[z][st], x - 1, y);
    } else {
      return query(t[z][st ^ 1], x - 1, y) + (1 << x);
    }
  }
}g;
LL gao(int o,int q){
  if(v[o].size()<=1)return 0;
  LL ans=0;
  for(int i=0;i<2;i++){
    if(g.t[o][i]){
      ans+=gao(g.t[o][i],q-1);
    }
  }
  if(!g.t[o][0]||!g.t[o][1])return ans;
  int x=g.t[o][0],y=g.t[o][1];
  if(v[x].size()>v[y].size())swap(x,y);
  LL cm=1e18;
  for(auto k:v[x]){
    cm=min(cm,1ll*g.query(y,q-1,k));
  }
  ans+=cm+(1<<q);
  return ans;
}
const int M=1e5+1;
vector<pair<int,int>>G[N];
int vis[N];
void dfs(int x,int t=0,int pre=0){
  a[x]=t;
  for(auto k:G[x]){
    if(k.fi!=pre){
      dfs(k.fi,t^k.se,x);
    }
  }
}
int main() {
  ios::sync_with_stdio(false);
  cin>>n;g.cnt=1;
  for(int i=1;i<n;i++){
    int s,t,w;
    cin>>s>>t>>w;
    ++s;++t;
    G[s].pb({t,w});
    G[t].pb({s,w});
  }
  dfs(1);
  for(int i=1;i<=n;i++)g.insert(1,30,a[i]);
  cout<<gao(1,30)<<'\n';
  return 0;
}
//200000*30
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值