文章目录
专题六 最小生成树
POJ 1251 Jungle Roads
题意: 给一张图,求最小生成树
题解: 模板提
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <map>
using namespace std;
typedef long long LL;
int const MAXN = 40;
int n, m, T, tot;
map<string, int> mp;
int g[MAXN][MAXN], dis[MAXN], st[MAXN];
// prime算法
int prime()
{
memset(dis, 0x3f, sizeof dis); // dis初始化
memset(st, 0, sizeof st);
int res = 0; // 记录最小生成树的距离和
for (int i = 0; i < n; ++i) // 外循环n次
{
int t = -1; // 记录到集合最小的点
for (int j = 1; j <= n; ++j)
if (!st[j] && (t == -1 || dis[j] < dis[t]))
t = j; // 找到t
if (i && dis[t] == 0x3f3f3f3f) return dis[t]; // 如果集合内有点且t点到集合的距离为无穷(即表示t不在连通图内), 不存在最小生成树
if (i) res += dis[t]; // 如果集合内有点,累加res
for (int j = 1; j <= n; ++j) // 更新所有与t连接的点
dis[j] = min(dis[j], g[t][j]);
st[t] = 1; // 把t放入集合
}
return res; // 返回res
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
while(cin >> n){
if (!n) break;
memset(g, 0x3f, sizeof g);
mp.clear();
tot = 0;
for (int i = 0, t; i < n - 1; ++i) {
string s1, s;
cin >> s1 >> t;
if (!mp.count(s1)) mp[s1] = ++tot;
for (int j = 0, tt; j < t; ++j) {
cin >> s >> tt;
if (!mp.count(s)) mp[s] = ++tot;
g[mp[s1]][mp[s]] = g[mp[s]][mp[s1]] = min(g[mp[s]][mp[s1]], tt);
}
}
cout << prime() << endl;
}
return 0;
}
POJ 1287 Networking
题意: 给定n个点m条边,求最小生成树。
题解: 裸题
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <map>
using namespace std;
int const N = 5e2 + 10, M = 1e5 + 10;
int g[N][N], dis[N], st[N];
int n, m;
// prime算法
int prime() {
memset(dis, 0x3f, sizeof dis); // dis初始化
memset(st, 0, sizeof st);
int res = 0; // 记录最小生成树的距离和
for (int i = 0; i < n; ++i) {// 外循环n次
int t = -1; // 记录到集合最小的点
for (int j = 1; j <= n; ++j)
if (!st[j] && (t == -1 || dis[j] < dis[t]))
t = j; // 找到t
if (i && dis[t] == 0x3f3f3f3f) return dis[t]; // 如果集合内有点且t点到集合的距离为无穷(即表示t不在连通图内), 不存在最小生成树
if (i) res += dis[t]; // 如果集合内有点,累加res
for (int j = 1; j <= n; ++j) // 更新所有与t连接的点
dis[j] = min(dis[j], g[t][j]);
st[t] = 1; // 把t放入集合
}
return res; // 返回res
}
int main() {
while (scanf("%d%d", &n, &m) != EOF) {
if (!n) break;
memset(g, 0x3f, sizeof g); // 初始化邻接矩阵
for (int i = 0; i < m; ++i) {// 读入边信息
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
g[a][b] = g[b][a] = min(g[a][b], c); // 无向图注意要赋值两次
}
cout << prime() << endl;
}
return 0;
}
POJ 2031 Building a Space Station
题意: 空间有很多球…
题解: 求任意2个球圆心距离,如果距离大于两个半径和,那么就是距离-半径和,要不然就是0,然后跑最小生成树
代码: 代码有点不想写了,贴上其他人的:https://blog.csdn.net/In_Youth/article/details/47080187?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <climits>
using namespace std;
double maz[109][109];
double d[109];
bool vis[109];
double dis(double a[], double b[])
{
double x, y, z, sum;
x = (a[0] - b[0]) * (a[0] - b[0]);
y = (a[1] - b[1]) * (a[1] - b[1]);
z = (a[2] - b[2]) * (a[2] - b[2]);
sum = sqrt((x + y + z) * 1.0);
sum = sum - (a[3] + b[3]);
return sum;
} //得到两点之间的距离!
void prim(int n)
{
int i, j, pos;
double sum, t;
for (i = 1; i <= n; ++i)
d[i] = maz[1][i];
memset(vis, false, sizeof(vis));
vis[1] = true;
sum = 0;
for (i = 0; i < n - 1; ++i)
{
t = INT_MAX;
for (j = 1; j <= n; ++j)
{
if (!vis[j] && d[j] < t)
{
t = d[j];
pos = j;
}
}
sum += t;
vis[pos] = true;
for (j = 1; j <= n; ++j)
{
if (!vis[j] && d[j] > maz[pos][j])
{
d[j] = maz[pos][j];
}
}
}
printf("%.3lf\n", sum);
}
int main()
{
int t, i, j;
double data[109][9], tem;
while (~scanf("%d", &t) && t)
{
for (i = 1; i <= t; ++i)
{
for (j = 1; j <= t; ++j)
maz[i][j] = i == j ? 0 : INT_MAX;
} //初始化maz数组
for (i = 1; i <= t; ++i)
{
for (j = 0; j < 4; ++j)
scanf("%lf", &data[i][j]);
} //保存原始数据
for (i = 1; i <= t; ++i)
{
for (j = i + 1; j <= t; ++j)
{
tem = dis(data[i], data[j]);
if (tem <= 0)
maz[i][j] = maz[j][i] = 0;
else
maz[i][j] = maz[j][i] = tem;
}
} //用两层for循环计算任意两点之间的距离,将最后结果保存在maz数组里
prim(t); //模板prim算法
}
return 0;
}
POJ 2421 Constructing Roads
题意: 输入一个n * n的矩阵,表示任意2个点的距离。然后给定q对数字a和b,表示a和b之间已经连通。现在要修公路,使得任意2个点联通,问最少要修多长的公路。
题解: 对于q对数字,记他们间的距离为0。然后跑最小生成树即可。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <map>
using namespace std;
int const N = 5e2 + 10, M = 1e5 + 10;
int g[N][N], dis[N], st[N];
int n, m;
// prime算法
int prime() {
memset(dis, 0x3f, sizeof dis); // dis初始化
memset(st, 0, sizeof st);
int res = 0; // 记录最小生成树的距离和
for (int i = 0; i < n; ++i) {// 外循环n次
int t = -1; // 记录到集合最小的点
for (int j = 1; j <= n; ++j)
if (!st[j] && (t == -1 || dis[j] < dis[t]))
t = j; // 找到t
if (i && dis[t] == 0x3f3f3f3f) return dis[t]; // 如果集合内有点且t点到集合的距离为无穷(即表示t不在连通图内), 不存在最小生成树
if (i) res += dis[t]; // 如果集合内有点,累加res
for (int j = 1; j <= n; ++j) // 更新所有与t连接的点
dis[j] = min(dis[j], g[t][j]);
st[t] = 1; // 把t放入集合
}
return res; // 返回res
}
int main() {
freopen("in.txt", "r", stdin);
while (scanf("%d%d", &n, &m) != EOF) {
if (!n) break;
memset(g, 0x3f, sizeof g); // 初始化邻接矩阵
for (int i = 0; i < m; ++i) {// 读入边信息
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
g[a][b] = g[b][a] = min(g[a][b], c); // 无向图注意要赋值两次
}
cout << prime() << endl;
}
return 0;
}
ZOJ 1586 QS Network
题意: 最小生成树裸题
题解: 最小生成树裸题
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <map>
typedef long long LL;
using namespace std;
int const N = 1010, M = N * 2;
int g[N][N], dis[N], st[N], cost[N];
int n, m;
// prime算法
int prime() {
memset(dis, 0x3f, sizeof dis); // dis初始化
memset(st, 0, sizeof st);
int res = 0; // 记录最小生成树的距离和
for (int i = 0; i < n; ++i) {// 外循环n次
int t = -1; // 记录到集合最小的点
for (int j = 1; j <= n; ++j)
if (!st[j] && (t == -1 || dis[j] < dis[t]))
t = j; // 找到t
if (i && dis[t] == 0x3f3f3f3f) return dis[t]; // 如果集合内有点且t点到集合的距离为无穷(即表示t不在连通图内), 不存在最小生成树
if (i) res += dis[t]; // 如果集合内有点,累加res
for (int j = 1; j <= n; ++j) // 更新所有与t连接的点
dis[j] = min(dis[j], g[t][j]);
st[t] = 1; // 把t放入集合
}
return res; // 返回res
}
int T;
int main() {
cin >> T;
while (T--) {
cin >> n;
memset(g, 0x3f, sizeof g); // 初始化邻接矩阵
for (int i = 1, t; i <= n; ++i) cin >> cost[i];
for (int i = 1; i <= n; ++i) {
for (int j = 1, t; j <= n; ++j) {
cin >> t;
g[i][j] = t + cost[i] + cost[j];
}
}
cout << prime();
if (T) cout << endl;
}
return 0;
}
POJ 1789 Truck History
题意: 给定n个字符串,每个字符串看作一个点,2个点间的距离为不同给定字符个数,球最小生成树。
题解: 裸题
代码:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <climits>
#include <string>
using namespace std;
int const N = 2e3 + 10, M = 1e5 + 10;
int g[N][N], dis[N], st[N];
int n, m;
string s[N];
// prime算法
int prime() {
memset(dis, 0x3f, sizeof dis); // dis初始化
memset(st, 0, sizeof st);
int res = 0; // 记录最小生成树的距离和
for (int i = 0; i < n; ++i) {// 外循环n次
int t = -1; // 记录到集合最小的点
for (int j = 1; j <= n; ++j)
if (!st[j] && (t == -1 || dis[j] < dis[t]))
t = j; // 找到t
if (i && dis[t] == 0x3f3f3f3f) return dis[t]; // 如果集合内有点且t点到集合的距离为无穷(即表示t不在连通图内), 不存在最小生成树
if (i) res += dis[t]; // 如果集合内有点,累加res
for (int j = 1; j <= n; ++j) // 更新所有与t连接的点
dis[j] = min(dis[j], g[t][j]);
st[t] = 1; // 把t放入集合
}
return res; // 返回res
}
int cal(int idx1, int idx2) {
int res = 0;
for (int i = 0; i < 7; ++i) res += (s[idx1][i] != s[idx2][i]);
return res;
}
int main() {
while(scanf("%d", &n) != EOF) {
if (!n) break;
memset(g, 0x3f, sizeof g); // 初始化邻接矩阵
for (int i = 1; i <= n; ++i) cin >> s[i];
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
g[i][j] = g[j][i] = cal(i, j);
}
}
printf("The highest possible quality is 1/%d.\n", prime());
}
return 0;
}
POJ 2349 Arctic Network
题意: 北极的某区域共有 n 座村庄,每座村庄的坐标用一对整数 (x,y) 表示。为了加强联系,决定在村庄之间建立通讯网络,使每两座村庄之间都可以直接或间接通讯。通讯工具可以是无线电收发机,也可以是卫星设备。无线电收发机有多种不同型号,不同型号的无线电收发机有一个不同的参数 d,两座村庄之间的距离如果不超过 d,就可以用该型号的无线电收发机直接通讯,d 值越大的型号价格越贵。现在要先选择某一种型号的无线电收发机,然后统一给所有村庄配备,数量不限,但型号都是相同的。配备卫星设备的两座村庄无论相距多远都可以直接通讯,但卫星设备是有限的,只能给一部分村庄配备。现在有 k 台卫星设备,请你编一个程序,计算出应该如何分配这 k 台卫星设备,才能使所配备的无线电收发机的 d 值最小。
题解: 把本题翻译过来就是求给定一个d,删去图中权值大于d的所有边,做最小生成树,得到的连通块数目要求小于等于k,求这么个d
通常的思路是二分d,然后求连通块个数,判断连通块个数和k的关系
但是kruskal具有单调性,枚举的边从小到大,集合的数目从大到小,具有单调性,因此不需要二分,只需要每次枚举完判断一下当前的集合个数是否等于k,如果等于k,那么说明当前放入的这条边的权值就是d。
代码: poj一直没编译过,放在其他平台交的
#include<bits/stdc++.h>
using namespace std;
typedef pair<double, double> PDD;
unordered_map<int, PDD> grid;
int n, k, cnt, m;
int const N = 5e2 + 10, M = N * N;
int fa[N];
struct Edge {
int a, b;
double w;
bool operator< (const Edge &W) {
return w < W.w;
}
}edge[M];
int get(int x) {
if (x == fa[x]) return x;
return fa[x] = get(fa[x]);
}
void kruskal() {
for (int i = 1; i <= cnt; ++i) fa[i] = i;
int cnt_con = cnt; // 记录连通块个数
if (cnt_con < k) {
cout << 0.00 << endl;
return;
}
// 从小到大枚举边
sort(edge, edge + m);
for (int i = 0; i < m; ++i) {
int a = edge[i].a, b = edge[i].b;
double w = edge[i].w;
int pa = get(a), pb = get(b);
if (pa != pb) {
fa[pa] = pb;
cnt_con--; // 每次加入边后,如果两个端点不在一个连通块内,那么合并后连通块个数将会减一
}
if (cnt_con == k) { // 一旦减到k,说明当前的边权值即为d
printf("%.2lf", w);
return;
}
}
}
int main() {
cin >> n >> k;
// 给每个节点标号
for (int i = 0; i < n; ++i) {
int x, y;
cin >> x >> y;
grid[++cnt] = {x, y};
}
// 计算每个节点间的距离
for (int i = 1; i <= cnt; ++i)
for (int j = 1; j <= cnt; ++j) {
int dx = grid[i].first - grid[j].first, dy = grid[i].second - grid[j].second;
edge[m++] = {i, j, sqrt(dx * dx + dy * dy)};
}
// 做最小生成树
kruskal();
return 0;
}
POJ 1751 Highways
题意: 给定n个村庄的坐标,需要使得这n个村庄联通。给定m条路,表示a村庄和b村庄联通,不需要修路。现在问要把所有村庄联通需要的最少花费为多少。
题解: 欧氏距离计算花费,对于已经修过的,那么花费为0.
代码: 和前面一个题目基本一样,没啥意思,不写了,贴上大佬的代码:https://www.cnblogs.com/alihenaixiao/p/4547542.html
#include <cmath>
#include <string>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 760;
const int INF = 0x3f3f3f3f;
const double Exp = 1e-10;
int cost[maxn][maxn], lowc[maxn];
int vis[maxn], pre[maxn];
//pre[i]记录i的前驱
struct point
{
int x, y;
};
int length (point a, point b)
{
return (a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y);
}
void prim (int n)
{
int i, j;
memset (vis, 0, sizeof(vis));
vis[1] = 1;
for (i=1; i<=n; i++)
{
pre[i] = 1;
lowc[i] = cost[1][i];
}
for (i=1; i<n; i++)
{
int p;
int mini = INF;
for (j=1; j<=n; j++)
if (!vis[j] && mini > lowc[j])
{
p = j;
mini = lowc[j];
}
if (cost[p][pre[p]] != 0)//需要建路
printf ("%d %d\n", pre[p], p);
vis[p] = 1;
for (j=1; j<=n; j++)
{
if (!vis[j] && lowc[j] > cost[p][j])
{
lowc[j] = cost[p][j];
pre[j] = p;
}
}
}
}
int main ()
{
int n;
point p[maxn];
scanf ("%d", &n);
for (int i=1; i<=n; i++)
{
scanf ("%d %d", &p[i].x, &p[i].y);
for (int j=1; j<i; j++)
cost[i][j] = cost[j][i] = length(p[i], p[j]);
cost[i][i] = 0;
}
int m;
scanf ("%d", &m);
while (m --)
{//把已经建过路的点聚集在一起
int x, y;
scanf ("%d %d", &x, &y);
cost[x][y] = cost[y][x] = 0;
}
prim(n);
return 0;
}
POJ 1258 Agri-Net
题意: 完全裸题,直接看别人的代码把:https://blog.csdn.net/versionwen/article/details/99686746
题解:
代码:
POJ 3026 Borg Maze
题意: 直接看别人题解吧,比较裸题:https://blog.csdn.net/qq_40421671/article/details/83041978
题解:
代码:
POJ 1679 The Unique MST
题意: 判断一个最小生成树是否唯一
题解: 先预处理一遍,求出最小生成树上的边,然后暴力枚举这些边,删除之后再跑一遍最小生成树,看是否和原来一样。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <map>
#include <vector>
using namespace std;
typedef long long LL;
LL n, m;
LL const N = 1000 + 10;
struct E {
LL u, v;
LL w;
}edge[N*N];
bool cmp(struct E a, struct E b) {
return a.w < b.w;
}
LL p[N*N], st[N*N];
vector<LL> used;
LL find(LL x) {
if (x != p[x]) p[x] = find(p[x]);
return p[x];
}
// kruskal算法
LL kruskal(LL cn) {
LL res = 0;
LL cnt = 0; // res记录最小生成树的权值,cnt记录当前最小生成树内有几条边
for (LL i = 1; i <= n; ++i) p[i] = i; // 并查集初始化
for (LL i = 0; i < m ; ++i) {// 枚举每一条边
if (cn==i) continue;
LL a = edge[i].u, b = edge[i].v;
LL c = edge[i].w; // 边a, b, 权值为c
a = find(a), b = find(b); // 得到a的父节点和b的父节点
if (a != b) {// 判断a和b是否在同一个集合内
res += c; // 在的话放入集合内
if (cn == -1) used.push_back(i);
cnt++;
p[a] = b;
}
}
if (cnt < n-1) return -1;
else return res;
}
LL T;
int main() {
cin >> T;
while(T--) {
used.clear();
cin >> n >> m;
for (LL i = 0, a, b, c; i < m; ++i) {
scanf("%lld%lld%lld", &edge[i].u, &edge[i].v, &edge[i].w);
/* E tmp;
tmp.u = a, tmp.v = b, tmp.w = c;
edge[i] = tmp;
*/
}
sort(edge , edge + m, cmp);
LL mst = kruskal(-1); // 最早的最小生成树
LL flg = 0;
for (LL i = 0; i < used.size(); ++i) { // 暴力枚举删除的边
LL idx = used[i]; // 边的下标
LL new_mst = kruskal(idx);
if (new_mst == mst) {
flg = 1;
break;
}
}
if (flg) cout << "Not Unique!\n";
else cout << mst << endl;
}
return 0;
}
HDU 1233 还是畅通工程
题意: 裸题
题解:
代码:
HDU 1301 Jungle Roads
题意: 裸题
题解:
代码:
HDU 1875 畅通工程再续
题意: 裸题
题解:
代码: