Borůvka算法与异或生成树

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. 对每个连通块,处理出与其他连通块连接的最小代价,并记录这条边。
  2. 连接所有连通块与其最小连接代价的连通块,并将该边边权计入。
  3. 若剩余连通块数量大于1,重复上述步骤。

代码

//#pragma comment(linker, "/STACK:102400000,102400000")
#include
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

题意

Borůvka算法与异或生成树_第1张图片

思路

异或最值问题,套一个trie,如果对这个套路不熟悉,请先做CF923C Perfect Security。
虽然已经没了Borůvka的形但是思想还在。
首先我们对所有点权排序并去重,因为相同的值异或为0,在图中必然是这些点相连且没有贡献,所以我们不用考虑这部分。
将剩下的点权插入trie,从根节点到叶子的一条路径就代表一个点权,叶子结点的数量就代表点权的数量。
初始时每个叶子都代表一个连通块,求解这个问题就是不断连接这些连通块的过程。
对于trie上的节点 x x x,如果其左右子树都存在,那么就必须找一条边连接其左右子树,若 x x x的所处位数为 w w w,则这条边必有 1 < < w 1<1<<w的贡献,对于 w w w之前的位数,因为 x x x到根节点路径相同异或值都为0;对于 [ w − 1 , 0 ] [w-1,0] [w1,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
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))//<
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;
}

你可能感兴趣的:(图论)