Borůvka算法
前几天才知道除了 p r i m prim prim和 k r u s k a l kruskal kruskal以外第三种求无向图MST的算法。
适用情况
平均
O
(
V
+
E
)
O(V+E)
O(V+E),最坏
O
(
(
V
+
E
)
l
o
g
V
)
O((V+E)logV)
O((V+E)logV)。
因为没有
k
r
u
s
k
a
l
kruskal
kruskal好写,所以一般不用于MST裸题。
相对于其他两种算法,适于处理边权由连接的两个点的点权通过某种计算方式得出的情况。
前置知识点
并查集、连通块
流程
- 对每个连通块,处理出与其他连通块连接的最小代价,并记录这条边。
- 连接所有连通块与其最小连接代价的连通块,并将该边边权计入。
- 若剩余连通块数量大于1,重复上述步骤。
代码
//#pragma comment(linker, "/STACK:102400000,102400000")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
//#define int ll
const int maxn=2e5+10,inf=0x3f3f3f3f,mod=1000000007;
const ll INF=0x3f3f3f3f3f3f3f3f;
const double eps=1e-9;
int fa[maxn];
int findfa(int x)
{
if(fa[x]!=x)
fa[x]=findfa(fa[x]);
return fa[x];
}
bool Merge(int x,int y)
{
int a=findfa(x),b=findfa(y);
if(a==b)
return 0;
if(a<b)
fa[b]=a;
else
fa[a]=b;
return 1;
}
struct edge
{
int u,v,w;
edge(){}
edge(int u,int v,int w):
u(u),v(v),w(w){}
};
vector<edge> E;
int boruvka(int n)
{
for(int i=1;i<=n;i++)//n个连通块
fa[i]=i;
int ans=0;
vector<int> cost(n+1),rec(n+1);
vector<bool> vis(E.size(),false);
while(1)
{
for(int i=1;i<=n;i++)
cost[i]=inf;//初始化为inf
int cur=0;//统计不同连通块
for(int i=0;i<E.size();i++)
{
int a=findfa(E[i].u),b=findfa(E[i].v),w=E[i].w;
if(a==b)
continue;
cur++;//记录a,b两个连通块连接的最小代价
if(w<cost[a])
cost[a]=w,rec[a]=i;//记录最小联通代价与相应边
if(w<cost[b])
cost[b]=w,rec[b]=i;
}
if(cur==0)
break;
for(int i=1;i<=n;i++)
{//最坏情况是剩余连通块数目减少一半
if(cost[i]<inf&&!vis[rec[i]])//与i相接的权值最小的边未加入
{
Merge(E[rec[i]].u,E[rec[i]].v);//连接两个连通块
ans+=E[rec[i]].w;
vis[rec[i]]=true;//防止重复计算
}
}
}
return ans;
}
signed main(int argc, char const *argv[])
{
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#ifdef DEBUG
freopen("input.in", "r", stdin);
// freopen("output.out", "w", stdout);
#endif
int n,m;
cin>>n>>m;
for(int i=1,u,v,w;i<=m;i++)
{
cin>>u>>v>>w;
E.push_back({u,v,w});
}
cout<<boruvka(n)<<endl;
return 0;
}
Xor-MST
题意
思路
异或最值问题,套一个trie,如果对这个套路不熟悉,请先做CF923C Perfect Security。
虽然已经没了Borůvka的形但是思想还在。
首先我们对所有点权排序并去重,因为相同的值异或为0,在图中必然是这些点相连且没有贡献,所以我们不用考虑这部分。
将剩下的点权插入trie,从根节点到叶子的一条路径就代表一个点权,叶子结点的数量就代表点权的数量。
初始时每个叶子都代表一个连通块,求解这个问题就是不断连接这些连通块的过程。
对于trie上的节点
x
x
x,如果其左右子树都存在,那么就必须找一条边连接其左右子树,若
x
x
x的所处位数为
w
w
w,则这条边必有
1
<
<
w
1<<w
1<<w的贡献,对于
w
w
w之前的位数,因为
x
x
x到根节点路径相同异或值都为0;对于
[
w
−
1
,
0
]
[w-1,0]
[w−1,0]的位数,这部分的贡献要对trie进行搜索来得到。
此时
x
x
x左右子树已经联通,回溯继续向上处理即可。
c
h
e
c
k
check
check最坏情况下一次遍历整个trie树,
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn),最多合并
n
l
o
g
n
nlogn
nlogn次。
整体时间复杂度
O
(
n
l
o
g
2
n
)
O(nlog^2n)
O(nlog2n)。
代码
//#pragma comment(linker, "/STACK:102400000,102400000")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
//#define int ll
const int inf=0x3f3f3f3f,mod=1000000007;
const ll INF=0x3f3f3f3f3f3f3f3f;
const double eps=1e-9;
//#define DEBUG//#define lowbit(x) ((x) & -(x))//<<setiosflags(ios::fixed)<<setprecision(9)
void read(){}
template<typename T,typename... T2>inline void read(T &x,T2 &... oth) {
x=0; int ch=getchar(),f=0;
while(ch<'0'||ch>'9'){if (ch=='-') f=1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
if(f)x=-x;
read(oth...);
}
const int maxn = 200005, M = 2;
int val[maxn],trie[maxn<<5][M],siz[maxn<<5],tot;
void init()
{
tot=1;
// memset(trie[0],0,sizeof(trie[0]));
// memset(isw,0,sizeof(isw));
}
void Insert(const int key)//rt传入0
{//trie插入模式串
int rt=0;
for(int i=30;i>=0;i--)
{
int id = (key>>i)&1;
if(!trie[rt][id])
trie[rt][id]=tot++;
rt = trie[rt][id];
}
}
ll check(int x,int y,int w)//启发式,最多向下2^w次?,但最多1e5个节点
{//在x子树和y子树上求得一个最小异或值
ll ret=LLONG_MAX;
if(trie[x][0]&&trie[y][0])//尽量先使高位相同
ret=min(ret,check(trie[x][0],trie[y][0],w-1));
if(trie[x][1]&&trie[y][1])
ret=min(ret,check(trie[x][1],trie[y][1],w-1));
if(ret==LLONG_MAX)
{
if(trie[x][0]&&trie[y][1])//w位此时一定为1了
ret=min(ret,check(trie[x][0],trie[y][1],w-1))+(1<<w);
if(trie[x][1]&&trie[y][0])
ret=min(ret,check(trie[x][1],trie[y][0],w-1))+(1<<w);
if(ret==LLONG_MAX)//说明两边没有节点
return 0;
}
return ret;
}
ll dfs(int x,int w)//节点x开始搜索,右数第w位
{//从高位向下dfs
if(w<0)//只有根节点为0
return 0;
if(trie[x][0]&&trie[x][1])//都存在,找最小边连接两个连通块
return (1<<w)+check(trie[x][0],trie[x][1],w-1)+dfs(trie[x][0],w-1)+dfs(trie[x][1],w-1);
else if(trie[x][0])//左面存在
return dfs(trie[x][0],w-1);
else//右面存在
return dfs(trie[x][1],w-1);
}
signed main(int argc, char const *argv[])
{
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#ifdef DEBUG
freopen("input.in", "r", stdin);
// freopen("output.out", "w", stdout);
#endif
int n,ans=0;
init();
cin>>n;
for(int i=1;i<=n;i++)
cin>>val[i];
sort(val+1,val+n+1);
n=unique(val+1,val+n+1)-val-1;//去重是因为相同值异或为0,没必要再计算
for(int i=1;i<=n;i++)
Insert(val[i]);
cout<<dfs(0,30)<<endl;
return 0;
}