Prim算法、Kruskal算法



前言

之前把Prim和Dijkstra给混了就没发,晚上花了十多分钟搞明白刷了几道题然后发哈哈哈哈(算是复习的其实)

一些注意点

  1. Prim和Dijkstra的区别
    区别在于,Prim是算该点到已加入的点的集合的距离最小值(即cost[u][v]),而Dijkstra是算该点到源点的最小值(即cost[u][v] + d[u])
  2. 最小生成树的表述
    最小生成树是使得图能连通,加边权值之和最小 的方案
    由Kruskal算法可知,每次加边都选择尽量小的边加上去,因此最后得到的图也是 最大边尽量小的图

Constructing Roads - HDU - 1102

题意
给定邻接矩阵,矩阵中的数值代表边的权值,再给定q条已连接的边,现求使整幅图能连通所需的边权值之和最小值
思路
MST模板题
只需把q条已连接边的权值设为0再跑最小生成树即可

Prim做法:

	#include <iostream>
	#include <cstdio>
	#include <queue>
	#include <cstring>
	#include <algorithm>
	using namespace std;
	const int N = 105;
	typedef long long ll;

	struct node{
		int cost, u;
		node(int pc, int pu): cost(pc), u(pu) {}
		bool operator < (const node& b) const { return cost > b.cost; }
	};
	int G[N][N];
	int mincost[N];
	int used[N];
	priority_queue<node> que;

	inline void init(){
		memset(mincost, 0x3f, sizeof(mincost));
		memset(used, false, sizeof(used));
	}

	int prim(int n){
		int ans = 0;
		mincost[1] = 0;
		que.push(node(0, 1));
		while(!que.empty()){
			int u = que.top().u;
			int cost = que.top().cost;
			que.pop();

			if(used[u] || mincost[u] < cost) continue;
			used[u] = true;
			mincost[u] = cost;
			ans += cost;

			for(int v = 1; v <= n; v++){
				if(u == v)  continue;
				if(!used[v] && mincost[v] > G[u][v]){
					mincost[v] = G[u][v];
					que.push(node(G[u][v], v));
				}
			}
		}
		return ans;
	}

	int main(){
		int n;
		while(~scanf("%d", &n)){
			init();
			for(int i = 1; i <= n; i++){
				for(int j = 1; j <= n; j++){
					scanf("%d", &G[i][j]);
				}
			}

			int q;
			scanf("%d", &q);
			while(q--){
				int u, v;
				scanf("%d%d", &u, &v);
				G[u][v] = G[v][u] = 0;
			}

			printf("%d\n", prim(n));
		}
	}

Kruskal做法:

	#include <iostream>
	#include <cstdio>
	#include <queue>
	#include <cstring>
	#include <algorithm>
	using namespace std;
	const int N = 105;
	typedef long long ll;

	struct edge{
		int u, v, val;
		edge() {}
		edge(int pu, int pv, int pval): u(pu), v(pv), val(pval) {}
		bool operator < (const edge& b)  const{
			return val < b.val;
		}
	};
	int G[N][N];
	int ft[N];
	edge e[N*N*2];
	int m;

	inline void init(int n){
		m = 0;
		for(int i = 1; i <= n; i++)     ft[i] = i;
	}

	int uffind(int x) { return x == ft[x] ? x : ft[x] = uffind(ft[x]); }
	void ufuni(int x, int y){
		int p = uffind(x), q = uffind(y);
		if(p != q)  ft[q] = p;
	}

	int Kruskal(int m){
		int ans = 0;
		sort(e, e + m);
		for(int i = 0; i < m; i++){
			int p = uffind(e[i].u), q = uffind(e[i].v);
			if(p != q){
				ans += e[i].val;
				ufuni(p, q);
			}
		}
		return ans;
	}

	int main(){
		int n;
		while(~scanf("%d", &n)){
			init(n);
			for(int i = 1; i <= n; i++){
				for(int j = 1; j <= n; j++){
					scanf("%d", &G[i][j]);
				}
			}

			int q;
			scanf("%d", &q);
			while(q--){
				int u, v;
				scanf("%d%d", &u, &v);
				G[u][v] = G[v][u] = 0;
			}

			for(int i = 1; i <= n; i++){
				for(int j = i + 1; j <= n; j++){
					e[m++] = edge(i, j, G[i][j]);
				}
			}

			printf("%d\n", Kruskal(m));
		}
	}


Networking - POJ - 1287

题意
给定u,v,val,求MST
思路
MST模板题,只是这次用的是邻接链表

	#include <iostream>
	#include <cstdio>
	#include <queue>
	#include <cstring>
	#include <algorithm>
	using namespace std;
	const int N = 105;
	typedef long long ll;

	struct node{
		int cost, u;
		node(int pc, int pu): cost(pc), u(pu) {}
		bool operator < (const node& b) const { return cost > b.cost; }
	};
	struct edge{
		int to, next, val;
	};
	edge e[N*N];
	int tot;
	int head[N];
	int mincost[N];
	int used[N];
	priority_queue<node> que;

	inline void init(){
		tot = 0;
		memset(head, -1, sizeof(head));
		memset(mincost, 0x3f, sizeof(mincost));
		memset(used, false, sizeof(used));
	}

	inline void addEdge(int u, int v, int val){
		e[tot].to = v;
		e[tot].val = val;
		e[tot].next = head[u];
		head[u] = tot++;
	}

	int prim(){
		int ans = 0;
		mincost[1] = 0;
		que.push(node(0, 1));
		while(!que.empty()){
			int u = que.top().u;
			int cost = que.top().cost;
			que.pop();

			if(used[u] || mincost[u] < cost) continue;
			used[u] = true;
			mincost[u] = cost;
			ans += cost;

			for(int i = head[u]; ~i; i = e[i].next){
				int v = e[i].to;
				if(u == v)  continue;
				if(!used[v] && mincost[v] > e[i].val){
					mincost[v] = e[i].val;
					que.push(node(e[i].val, v));
				}
			}
		}
		return ans;
	}

	int main(){
		int n, m;
		while(scanf("%d", &n) && n){
			init();
			scanf("%d", &m);
			while(m--){
				int u, v, val;
				scanf("%d%d%d", &u, &v, &val);
				addEdge(u, v, val);
				addEdge(v, u, val);
			}

			printf("%d\n", prim());
		}
	}


畅通工程再续 - HDU - 1875

思路
先算距离,然后把距离<10或者>1000的边权值设为inf再跑MST,最终如果有点的used = false那就说明整幅图没有连通,无法完成

	#include <iostream>
	#include <cstdio>
	#include <queue>
	#include <cstring>
	#include <cmath>
	#include <algorithm>
	using namespace std;
	const int N = 505;
	typedef long long ll;
	const int inf = 0x3f3f3f3f;

	struct node{
		double cost;
		int u;
		node(double pc, int pu): cost(pc), u(pu) {}
		bool operator < (const node& b) const { return cost > b.cost; }
	};
	double G[N][N];
	double mincost[N];
	int used[N];
	int d[N][2];
	priority_queue<node> que;

	inline void init(){
		for(int i = 0; i < N; i++)  mincost[i] = inf;
		memset(used, false, sizeof(used));
	}

	double prim(int n){
		double ans = 0;
		mincost[0] = 0;
		que.push(node(0, 0));
		while(!que.empty()){
			int u = que.top().u;
			double cost = que.top().cost;
			que.pop();

			if(used[u] || mincost[u] < cost)    continue;

			used[u] = true;
			mincost[u] = cost;
			ans += cost*100;

			for(int v = 0; v < n; v++){
				if(u == v || G[u][v] == inf)  continue;
				if(!used[v] && mincost[v] > G[u][v]){
					mincost[v] = G[u][v];
					que.push(node(G[u][v], v));
				}
			}
		}
		return ans;
	}

	int main(){
		int t;
		scanf("%d", &t);
		while(t--){
			init();
			int n;
			scanf("%d", &n);
			for(int i = 0; i < n; i++)  scanf("%d%d", &d[i][0], &d[i][1]);
			for(int i = 0; i < n; i++){
				for(int j = 0; j < n; j++){
					if(i == j){
						G[i][j] = 0;
					}else{
						double dis = sqrt((d[i][0] - d[j][0])*(d[i][0] - d[j][0]) + (d[i][1] - d[j][1])*(d[i][1] - d[j][1]));
						if(dis < 10 || dis > 1000)  dis = (double)inf;
						G[i][j] = G[j][i] = dis;
					}
				}
			}

			double ans = prim(n);
			for(int i = 0; i < n; i++){
				if(!used[i]){
					ans = -1;
					break;
				}
			}

			if(ans == -1){
				puts("oh!");
			}else{
				printf("%.1f\n", ans);
			}
		}
	}


Truck History - POJ - 1789

题意
(简化版,英文原题十分难懂T^T)
给定一组7个字符的字符串,字符串两两之间的距离为对应位置上不同字母数之和,最开始只有一个字符串,后来的字符串由其他字符串派生出来,派生的代价为两个字符串的距离,现求得到所有字符串的代价之和最小值
思路
先算距离,然后跑MST

	#include <iostream>
	#include <cstdio>
	#include <queue>
	#include <cstring>
	#include <algorithm>
	using namespace std;
	const int N = 2005;
	typedef long long ll;

	struct node{
		int cost, u;
		node(int pc, int pu): cost(pc), u(pu) {}
		bool operator < (const node& b) const { return cost > b.cost; }
	};
	int G[N][N];
	int mincost[N];
	bool used[N];
	char s[N][10];
	priority_queue<node> que;

	inline void init(){
		memset(G[0], 0, sizeof(G));
		memset(mincost, 0x3f, sizeof(mincost));
		memset(used, false, sizeof(used));
	}

	int prim(int n){
		int ans = 0;
		mincost[0] = 0;
		que.push(node(0, 0));
		while(!que.empty()){
			int u = que.top().u;
			int cost = que.top().cost;
			que.pop();

			if(used[u] || mincost[u] < cost) continue;
			used[u] = true;
			mincost[u] = cost;
			ans += cost;

			for(int v = 0; v < n; v++){
				if(u == v)  continue;
				if(!used[v] && mincost[v] > G[u][v]){
					mincost[v] = G[u][v];
					que.push(node(G[u][v], v));
				}
			}
		}
		return ans;
	}

	int main(){
		int n;
		while(~scanf("%d", &n) && n){
			init();
			for(int i = 0; i < n; i++)  scanf("%s", s[i]);
			for(int i = 0; i < n; i++){
				for(int j = 0; j < i; j++){
					for(int k = 0; k < 7; k++){
						if(s[i][k] != s[j][k]){
							G[i][j]++;
						}
						G[j][i] = G[i][j];
					}
				}
			}

			printf("The highest possible quality is 1/%d.\n", prim(n));
		}
	}


旅行 - SZUCPC 2017 Winter

思路
本题似乎只能用Kruskal,因为需要用并查集,另外询问次数很多因此需要离线打表
首先无疑是MST,因为价值取最大的那条路
然后用并查集动态维护每个连通块点的个数和美丽城市的个数,并动态计算出加入每一条边后答案点对的和
因为使用Kruskal,边值是升序排序的,因此最终可通过二分寻找最后一个不超过承受能力的答案并输出

  #include <cstdio>
  #include <algorithm>
  using namespace std;
  typedef long long ll;
  const int N = 2e5 + 15;

  struct edge{
      int u, v, val;
      bool operator < (const edge& b) const { return val < b.val; }
  };

  int ft[N];
  int f[N];
  int s_sum[N], s_beauty[N];
  bool beauty[N];
  edge e[N];

  void mInit(int n){
      e[0].val = 0, f[0] = 0;
      for(int i = 1; i <= n; i++){
          ft[i] = i;
          s_sum[i] = 1;
          s_beauty[i] = beauty[i];
      }
  }

  int mFind(int x){ return ft[x] == x ? x : ft[x] = mFind(ft[x]); }
  void mUnique(int x, int y){
      int p = mFind(x), q = mFind(y);
      if(p != q){
          ft[q] = p;
          s_sum[p] += s_sum[q];
          s_beauty[p] += s_beauty[q];
      }
  }

  void Kruskal(int n){
      sort(e + 1, e + 1 + n);
      for(int i = 1; i <= n; i++){
          int u = e[i].u, v = e[i].v;
          f[i] = f[i - 1];
          int p = mFind(u), q = mFind(v);
          if(p != q){
              f[i] += (s_sum[p] * s_beauty[q] + s_sum[q] * s_beauty[p]);
              mUnique(u, v);
          }
      }
  }

  int main(){
      int t;
      scanf("%d", &t);
      while(t--){
          int m, n, q;
          scanf("%d%d%d", &m, &n, &q);
          for(int i = 1; i <= m; i++)     scanf("%d", &beauty[i]);
          for(int i = 1; i <= n; i++)     scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].val);
          mInit(m);

          Kruskal(n);
          for(int i = 1; i <= q; i++){
              edge k;
              scanf("%d", &k.val);
              int pos = upper_bound(e + 1, e + 1 + n, k) - e - 1;
              printf("%d\n", f[pos]);
          }
      }
  }