zoukankan      html  css  js  c++  java
  • 最小生成树

    前言

    这只蒟蒻由于一道需要最小生成树的题只会打Kruskal而逼近超时

    心血来潮学了Prim

    其实都挺简单的(为什么我当初就不会Prim)

    update 2021.7.17 新搞懂了 (Boruvka) 算法。

    一、Kruskal算法

    适用于稀疏图。

    其实很简单的,你只要对于每一条边,按边权从小打大排序,如果两个点没有在一个连通块中,那么连接,累加权值,连接用并查集维护。

    连几条边呢?

    当然是n-1条。

    Kruskal
    void MST()
    {
    	n = Read();
    	m = Read();
    	for(int i = 1;i <= n;++ i)
    		f[i] = i;
    	for(int i = 1;i <= m;++ i)
    	{
    		int dz1 = Read(),dz2 = Read(),dz3 = Read();
    		q.push(node(dz1,dz2,dz3));
    	}
    	int tot = 0,ans = 0;
    	while(tot < n - 1)
    	{
    		node t = q.top();
    		q.pop();
    		int u1 = findSet(t.u),v1 = findSet(t.v);
    		if(u1 != v1)
    		{
    			f[u1] = v1;
    			ans += t.s;
    			++ tot;
    		}
    	}
    	printf("%d",ans);
    }
    

    时间复杂度:(O(mlog(m)))((m)为边数)。

    如果是完全图,时间复杂度就不能忍受了:(O(n^2/2*log(n^2/2)))

    其实就是:(O(n^2log(n^2)))

    那么就要引出今天的主角:

    二、Prim算法

    适用于稠密图。

    其实这个也不难,我们需要一个dis数组存当前如果要连到第i个点需要的最小权值,vis数组存这个点是否已经加入了最小生成树。

    我们把起点默认设为一号点,预处理出一号点到到其他点的最小距离,即预处理出(dis)数组,(vis[1])当然要赋值为1,然后开始加边,当然也是n-1次,每次找出最小的(dis[i])且i没有被加入到最小生成树中,加入它(自行YY怎么写),然后更新(dis[i])

    时间复杂度:(O(n^2))

    是不是很快啊,少了一个(log(n))呢!

    Prim
    void MST()
    {
    	n = Read();
    	m = Read();
    	for(int i = 1;i <= m;++ i)
    	{
    		int x = Read(),y = Read(),z = Read();
    		Add_Edge(x,y,z);
    		Add_Edge(y,x,z);
    	}
    	memset(dis,INF,sizeof(dis));
    	vis[1] = 1;
    	for(int i = head[1]; i ;i = e[i].nxt)
    		dis[e[i].v] = Min(dis[e[i].v],e[i].s);//有重边要用Min值 
    	for(int i = 1;i < n;++ i)
    	{
    		int ID,MIN = INF;
    		for(int j = 1;j <= n;++ j)
    			if(!vis[j] && dis[j] < MIN)
    			{
    				MIN = dis[j];
    				ID = j;
    			}
    		ans += MIN;
    		vis[ID] = 1;
    		for(int i = head[ID]; i ;i = e[i].nxt)
    			dis[e[i].v] = Min(dis[e[i].v],e[i].s);
    	}
    	printf("%d",ans);
    }
    

    以上两份代码要建立在是连通图上,否则会出现问题!

    三、Boruvka算法

    其实跟前面两种都差不多,本质上还是贪心。

    我们考虑维护每个连通块,初始时 (n) 个点自然就有 (n) 个连通块。

    每一次操作 (O(m)) 求出每个连通块连向其它连通块的最短边,然后连起来。

    可以发现一次操作下来最劣情况连通块减半,最优情况直接连通,所以时间复杂度是 (O(mlog_2n)) 的。

    实现的时候其实是 (O((n+m)log_2n)) 的。

    Boruvka
    struct edge
    {
    	int u,v,w;
    	void Get(){u = Read();v = Read();w = Read();}
    }e[MAXM];
    int f[MAXN],dis[MAXN],to[MAXN];
    int findSet(int x)
    {
    	if(f[x] != x) f[x] = findSet(f[x]);
    	return f[x];
    }
    bool unionSet(int x,int y)
    {
    	int u = findSet(x),v = findSet(y);
    	if(u == v) return 0;
    	if(u > v) swap(u,v);
    	f[v] = u;
    	return 1;
    }
    
    int main()
    {
    //	freopen(".in","r",stdin);
    //	freopen(".out","w",stdout);
    	n = Read(); m = Read();
    	for(int i = 1;i <= m;++ i) e[i].Get();
    	for(int i = 1;i <= n;++ i) f[i] = i,dis[i] = INF,to[i] = 0;
    	int cnt = n;
    	while(cnt > 1)
    	{
    		for(int i = 1,u,v;i <= m;++ i)
    		{
    			u = findSet(e[i].u);
    			v = findSet(e[i].v);
    			if(u != v)
    			{
    				if(e[i].w < dis[u]) dis[u] = e[i].w,to[u] = v;
    				if(e[i].w < dis[v]) dis[v] = e[i].w,to[v] = u;
    			}
    		}
    		bool f = 0;
    		for(int i = 1;i <= n;++ i)
    		{
    			if(findSet(i) == i && to[i])
    				if(unionSet(i,to[i]))
    					ans += dis[i],cnt--,f = 1;
    			dis[i] = INF; to[i] = 0; 
    		}
    		if(!f) {printf("orz");return 0;}
    	}
    	Put(ans);
    	return 0;
    }
    

    但是 Boruvska 算法更有前途,为什么这么说呢?因为在某些题里面,它一轮更新的时间复杂度并不需要 (O(m))

    比如CF888G Xor-MST(洛谷)

    (n) 个点,每个点有权值 (a_i),两个点 (i,j) 连边的代价为 (a_i oplus a_j),求最小生成树。

    对于这道题,上面两个算法显得无能为力,而普通版本的 Boruvka 也做不了,但是改一改就行了。

    我们注意到在一轮更新的时候每个点可以在 (O(log_2 a_i)) 的时间内通过权值线段树/Trie找到最小的连向其它连通块的边,至于维护连通性可以用并查集,顺便也可以用线段树合并维护其它点的Trie。

    时间复杂度可以做到 (O(nlog_2a_ilog_2n))

    或者我们把这玩意叫做最小异或生成树

    还有个小常数的权值分治做法,可以在这里的T3看到更详细的讲解。

    最小异或生成树
    //12252024832524
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define TT template<typename T>
    using namespace std; 
    
    typedef long long LL;
    const int MAXN = 200005;
    const int INF = 1 << 30;
    int n;
    
    LL Read()
    {
    	LL x = 0,f = 1;char c = getchar();
    	while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
    	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
    	return x * f;
    }
    TT void Put1(T x)
    {
    	if(x > 9) Put1(x/10);
    	putchar(x%10^48);
    }
    TT void Put(T x,char c = -1)
    {
    	if(x < 0) putchar('-'),x = -x;
    	Put1(x); if(c >= 0) putchar(c);
    }
    TT T Max(T x,T y){return x > y ? x : y;}
    TT T Min(T x,T y){return x < y ? x : y;}
    TT T Abs(T x){return x < 0 ? -x : x;}
    
    bool vis[MAXN];
    int od[MAXN],f[MAXN],val[MAXN];
    bool cmp(int x,int y){return val[x] < val[y];}
    int findSet(int x)
    {
    	if(f[x]^x) f[x] = findSet(f[x]);
    	return f[x];
    }
    
    int rt[MAXN],tot,who;
    int ch[MAXN*60][2],siz[MAXN*60],ID[MAXN*60];
    void Add(int &x,int v,int rk)
    {
    	if(!x) x = ++tot;
    	++siz[x];
    	if(rk < 0) {ID[x] = who;return;}
    	bool to = v >> rk & 1;
    	Add(ch[x][to],v,rk-1);
    }
    int mge(int x,int y)
    {
    	if(!x || !y) return x|y;
    	siz[x] += siz[y];
    	ch[x][0] = mge(ch[x][0],ch[y][0]);
    	ch[x][1] = mge(ch[x][1],ch[y][1]);
    	return x;
    }
    void unionSet(int u,int v)
    {
    	u = findSet(u); v = findSet(v);
    	if(u^v) f[v] = u,rt[u] = mge(rt[u],rt[v]);
    }
    int MIN[MAXN],CID[MAXN],ori;
    void Query(int lst,int x,int v,int rk)
    {
    	if(v >= MIN[who]) return;
    	if(rk < 0)
    	{
    		CID[who] = ID[lst];
    		MIN[who] = v;
    		return;
    	}
    	bool to = val[ori] >> rk & 1;
    	if(siz[ch[lst][to]] - siz[ch[x][to]]) Query(ch[lst][to],ch[x][to],v,rk-1);
    	else Query(ch[lst][to^1],ch[x][to^1],v|(1<<rk),rk-1);
    }
    
    int main()
    {
    //	freopen("xor.in","r",stdin);
    //	freopen("xor.out","w",stdout);
    	n = Read();
    	for(int i = 1;i <= n;++ i) val[i] = Read(); 
    	for(int i = 1;i <= n;++ i) od[i] = f[i] = i,MIN[i] = INF;
    	int cnt = n;
    	sort(od+1,od+n+1,cmp);
    	for(int i = 2;i <= n;++ i)
    		if(val[od[i]] == val[od[i-1]])
    			unionSet(od[i-1],od[i]),--cnt;
    	for(int i = 1;i <= n;++ i)
    		if(findSet(i) == i)
    			who = i,Add(rt[i],val[i],29),Add(rt[0],val[i],29);
    	LL ans = 0;
    	while(cnt > 1)
    	{
    		for(int i = 1;i <= n;++ i)
    		{
    			who = findSet(i); ori = i;
    			Query(rt[0],rt[who],0,29);
    		}
    		for(int i = 1;i <= n;++ i)
    		{
    			if(MIN[i] < INF && findSet(i) != findSet(CID[i]))
    			{
    				unionSet(i,CID[i]);
    				ans += MIN[i];
    				MIN[i] = INF;
    				--cnt;
    			}
    		}
    	}
    	Put(ans);
    	return 0;
    }
    

    练习

    板题(洛谷)

    CF888G Xor-MST(洛谷)

  • 相关阅读:
    Android基础知识
    使用Android Studio和Genymotion模拟器搭建Andriod开发环境
    【原】Java开发环境搭建
    【转】JavaScript顶级对象参考模型
    【转】在.Net中关于AOP的实现
    【转】各版本IIS下ASP.net请求处理过程区别
    【转】我们应该如何去了解JavaScript引擎的工作原理
    Linux系统编程(27)——线程控制
    Linux系统编程(26)——守护进程
    Linux系统编程(25)——终端
  • 原文地址:https://www.cnblogs.com/PPLPPL/p/13395344.html
Copyright © 2011-2022 走看看