【BHOJ ModricWang的布线问题 I、II】最小生成树 | Kruskal | 并查集 | std::sort | N

ModricWang的布线问题 I 和 II 

ModricWang的布线问题    ModricWang的布线问题 II

I 就是裸的最小生成树,跟拮据的模拟城市的代码是一样的

II 多了一个小限制,就是有一个顶点只能连一条边

下面就分析 II:

题目描述

ModricWang需要将n台计算机连接起来,不同的2台计算机之间的连接费用可能是不同的。为了节省费用,我们考虑采用间接数据传输结束,就是一台计算机可以间接地通过其他计算机实现和另外一台计算机连接。

为了使得任意两台计算机之间都是连通的(不管是直接还是间接的),需要在若干台计算机之间用网线直接连接,现在想使得总的连接费用最省,让你编程计算这个最小的费用。

需要注意的是,ModricWang的计算机并不全都很强。他有一台计算机的承载能力有限,只能接入一根网线。

输入

CF风格输入,第一行 V E k,k是那个特殊顶点  2≤ V ≤10000,2≤ E ≤600000

接下来E行,每行三个整数u,v,d ,是无向图的边

(题目保证一定存在可行的连通方案, 数据中可能存在权值不一样的重边,但是保证没有自环)

PS:Kruskal是不怕重边的,但是怕自环

输出

输出只有一行一个整数,最小总连接费用。

输入样例

4 4 4 
1 2 1
1 3 2
2 3 1
1 4 2

输出样例

4

分析

布线 II 多了一个小限制,就是有一个顶点只能连一条边。思路大概如下:

  1. 要么就利用一个bool标记,然后在遍历边的时候特判
  2. 要么就用top++的方式动态读边,把跟k相连的边都不读进数组,同时单独维护一个连接k的最短边,然后在Kruskal之前把sum初始化为这个最短路(不再是0了),然后对除了k以外的其他点做最小生成树(因为k一条边都没有,自己相当于是一个极大连通分量)。所以在std::sort边和Kruskal之前要重设 E,以及自减V
  3. 2这样做正确的原因是  跟k相连的边必选一条(否则不能生成最小生成树) 并且必选最短的(否则得不到最小花费解)

代码 

首先是方案 1 的代码。有两种设计特判的逻辑。

其中有一种是不能用bool merge的

#include <cstdio>
#include <cstring>
#include <algorithm>
#define sc(x) {register char _c=getchar(),_v=1;for(x=0;_c<48||_c>57;_c=getchar())if(_c==45)_v=-1;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=getchar());x*=_v;}
void PRT(long long a){if(a<0)putchar(45),a=-a;if(a>=10)PRT(a/10);putchar(a%10+48);}

constexpr int MV(10005);
constexpr int ME(600005);

namespace kstl
{

class UnionFind
{
	private:
		int uf[MV];

	public:

		UnionFind(void) { }

		inline void init(int V)
		{
			memset(uf, -1, (V+1) * sizeof(int));
		}

		int find(int x)
		{
			if (uf[x] >= 0)
				return uf[x] = find(uf[x]);
			return x;
		}

		void merge(int x, int y)
		{
			int r1 = find(x);
			int r2 = find(y);
			if (r1<r2)
			{
				uf[r1] += uf[r2];
				uf[r2] =  r1;
			}
			else if(r2<r1)
			{
				uf[r2] += uf[r1];
				uf[r1] =  r2;
			}
		}
};

}	// namespace kstl

int V, E;
kstl::UnionFind uf;

struct Edge
{
	int u, v;
	long long cost;

	Edge(void) { }
	Edge(int u, int v, long long cost) :
		u(u), v(v), cost(cost) { }

	inline bool operator < (const Edge &e) const
	{
		return cost < e.cost;
	}
} edge[ME];

int main()
{
	int k;
	bool link_k = false;
	long long sum = 0;
	sc(V)sc(E)sc(k)
	uf.init(V);
	register Edge *it=edge;
	for (auto End=edge+E; it<End; ++it)
	{
		sc((it->u))sc((it->v))sc((it->cost))
	}
	std::sort(edge, edge+E);
	for (int e=0; e<E; e++)
	{
		int u = edge[e].u, v = edge[e].v;
		
		/* 如果是这样设置if结构(先用并查集判断)来特判,就不能通过返回bool的merge来简化并查集操作了 
		   比如 A、B、C、k两两相连,ABC是正三角形,k在中心 
		   然后Kruskal的时候先把Ak连了 
		   然后再遍历到Bk的时候,如果用的是bool merge,那么并查集就把B和k连起来了,
		   但是这条边实际上是没有被选中的,sum也不会+=Bk.cost,但是并查集居然把它们连起来了
		   接着是Ck,然后sum也没+=Ck.cost,但是并查集又连起来了
		   然后之后的AB、AC、BC都不能选了,因为并查集认为选它们会形成环 
		   然后就没法生成正确的最小生成树了 
		*/ 
		if (uf.find(u) != uf.find(v))
		{
			if (u == k || v == k)
			{
				if (!link_k)
				{
					uf.merge(u, v);
					link_k = true;
					sum += edge[e].cost;
				}
			}
			else sum += edge[e].cost, uf.merge(u, v);
		}
	}
	PRT(sum);
}


int main()
{
//	while(1)
//	{
		int k;
		se(V)sc(E)sc(k)
		int sum = 0;
		uf.init(V);
		register Edge *it=edge;
		for (auto End=edge+E; it<End; ++it)				// 指针快读
		{
			sc((it->u))sc((it->v))sc((it->cost))
		}
		std::sort(edge,edge+E);
		bool not_used = true;
		int coll = 1;
		
		/* 这样的话就还是可以用bool merge 
		   就是先判断是不是k边,如果不是就完全可以用bool merge
		   如果是,就接着判断k连过没有,没连过就可以无脑merge,bool还是void都无所谓,连过就是直接不管这条边了 
		*/ 
		for (int e=0; e<E&&coll!=V; e++)				// collected == V 的时候说明已经收纳了V-1条边了,已经形成最小生成树
		{
			if (edge[e].u == k || edge[e].v == k)
			{
				if (not_used)
				{
					not_used = false;
					// k结点都没用过,所以可以无脑合并 
					uf.merge(edge[e].u, edge[e].v);
					sum += edge[e].cost,++coll;
				}
				// k用过了,直接不管这条边 
			}
			
			else // 都是普通边,正常操作(和ModricWang的布线问题I 一样) 
			{
				if (uf.merge(edge[e].u, edge[e].v))			// 如果返回true则说明二者不在同一个集合,并进行路径压缩+按规模合并
				sum += edge[e].cost,++coll;
			}
		}
			
		PRT(sum),putchar(10);
//	}
}

然后是方案2的代码:

#include <cstdio>
#include <cstring>
#include <algorithm>

#define sc(x) {register char _c=getchar(),_v=1;for(x=0;_c<48||_c>57;_c=getchar())if(_c==45)_v=-1;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=getchar());x*=_v;}
#define se(x) {register char _c=getchar(),_v=1;for(x=0;_c<48||_c>57;_c=getchar())if(_c==45)_v=-1;else if(_c==-1)return 0;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=getchar());x*=_v;}
void PRT(int a){if(a>=10)PRT(a/10);putchar(a%10+48);}

constexpr int MV(10005);
constexpr int ME(600007);

namespace kstl
{

class UnionFind
{
	private:
		int uf[MV];

	public:

		UnionFind(void) { }

		inline void init(int V)
		{
			memset(uf, -1, (V+1) * sizeof(int));
		}

		int find(int x)
		{
			if (uf[x] >= 0)
				return uf[x] = find(uf[x]);
			return x;
		}

		bool merge(int x, int y)
		{
			int r1 = find(x);
			int r2 = find(y);
			if (r1<r2)
			{
				uf[r1] += uf[r2];
				uf[r2] =  r1;
				return true;
			}
			else if(r2<r1)
			{
				uf[r2] += uf[r1];
				uf[r1] =  r2;
				return true;
			}
			return false;
		}
};

}	// namespace kstl

int V, E;
kstl::UnionFind uf;

struct Edge
{
	int u, v, cost;

	Edge(void) { }
	Edge(int u, int v, int cost) :
		u(u), v(v), cost(cost) { }

	inline bool operator < (const Edge &e) const		// 用Kruskal需要对边进行排序
	{
		return cost < e.cost;
	}
} edge[ME];

int main()
{
//	while(1)
//	{
		int k;
		se(V)sc(E)sc(k)
		int min_k_edge = 0x7fffffff;	// 维护这个k的最短边 
		uf.init(V);
		int top = 0;					// 一切跟k相连的边都不读,边数不再是E了 
		while(E--)
		{
			int u,v,d;
			sc(u)sc(v)sc(d)
			if(u==k||v==k)				// 不存边,只更新k的最短边 
			{
				if (min_k_edge > d)
					min_k_edge = d;
			}
			else edge[top].u=u,edge[top].v=v,edge[top++].cost=d;
		}
		E = top, --V;					// --V是因为相当于删除了一个点
        int sum = min_k_edge;			// 最小生成树花费和的初始化就是这个最短边


        // 接下来的代码就跟布线 I 一样了,也是用的bool merge
		std::sort(edge,edge+E);

		int coll = 1;
		for (int e=0; e<E&&coll!=V; e++)				// collected == V 的时候说明已经收纳了V-1条边了,已经形成最小生成树
			if (uf.merge(edge[e].u, edge[e].v))			// 如果返回true则说明二者不在同一个集合,并进行路径压缩+按规模合并
				sum += edge[e].cost,++coll;
			
		PRT(sum),putchar(10);
//	}
}

下次再把Prim贴上~

猜你喜欢

转载自blog.csdn.net/weixin_42102584/article/details/83220397