大概就是:初始每个点都是独立的集合,每次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;
}
把所有值建一个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
因为题目要求每时每刻必须联通,且环上异或为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