《挑战程序设计竞赛》2.5 最小生成树 POJ3723 3169 1258 2377 2395 AOJ2224(1)
POJ3723
http://poj.org/problem?id=3723
题意
windy要组建一支军队,召集了N个女孩和M个男孩,每个人要付10000RMB,但是如果一个女孩和一个男孩有关系d的,且已经付给了其中一个人的钱,那么就可以付给另一个人10000-d元,求windy最少要付多少钱。
思路
题目所给的数据是两两之间的连通关系,比较适合用kruskal+并查集求解。
但这个题要求的是最大生成树,不是最小生成树哦,需要修改比较条件。当然将d取反再求最小生成树也是一样的。
代码
Source Code
Problem: 3723 User: liangrx06
Memory: 996K Time: 360MS
Language: C++ Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 20000;
const int R = 50000;
struct Node {
int x, y, d;
};
int n, r;
Node node[R+1];
int pre[N+1];
int rank[N+1];
bool cmp(const Node& a, const Node& b)
{
return a.d < b.d;
}
void init()
{
for (int i = 1; i <= n; i ++) {
pre[i] = i;
rank[i] = 0;
}
}
int find(int a)
{
while (a != pre[a])
a = pre[a];
return a;
}
void unite(int a, int b)
{
a = find(a);
b = find(b);
if (a == b) return;
if (rank[a] < rank[b]) {
pre[a] = b;
} else {
pre[b] = a;
if (rank[a] == rank[b])
rank[a] ++;
}
}
bool same(int a, int b)
{
return find(a) == find(b);
}
int kruskal()
{
sort(node+1, node+r+1, cmp);
int res = 0;
for (int i = 1; i <= r; i ++) {
int x = node[i].x, y = node[i].y;
if (!same(x, y)) {
unite(x, y);
res += node[i].d;
}
}
return res;
}
int main(void)
{
int t, n1, n2;
cin >> t;
while (t--) {
cin >> n1 >> n2 >> r;
n = n1 + n2;
int x, y, d;
for (int i = 1; i <= r; i ++) {
scanf("%d%d%d", &x, &y, &d);
node[i].x = x + 1;
node[i].y = n1 + y + 1;
node[i].d = -d;
}
init();
printf("%d\n", 10000 * n + kruskal());
}
return 0;
}
POJ3169
POJ1258
http://poj.org/problem?id=1258
题意
有n个农场,已知这n个农场都互相相通,有一定的距离,现在每个农场需要装光纤,问怎么安装光纤能将所有农场都连通起来,并且要使光纤距离最小,输出安装光纤的总距离。
思路
求最小生成树,由于这个题直接给出了邻接矩阵,所以用prim算法比较合适。
代码
Source Code
Problem: 1258 User: liangrx06
Memory: 212K Time: 16MS
Language: C++ Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 100;
const int INF = 0x3f3f3f3f;
int n;
int d[N][N];
int md[N];
bool used[N];
int prim()
{
fill(md, md+n, INF);
fill(used, used+n, false);
md[0] = 0;
int res = 0;
while (true) {
int v = -1;
for (int i = 0; i < n; i ++) {
if (!used[i] && (v == -1 || md[i] < md[v]))
v = i;
}
if (v == -1) break;
used[v] = true;
res += md[v];
for (int i = 0; i < n; i ++) {
md[i] = min(md[i], d[v][i]);
}
}
return res;
}
int main(void)
{
while (scanf("%d", &n) != EOF) {
for (int i = 0; i < n; i ++) {
for (int j = 0; j < n; j ++) {
scanf("%d", &d[i][j]);
}
}
printf("%d\n", prim());
}
return 0;
}
POJ2377
http://poj.org/problem?id=2377
题意
给定一些两点之间的连通距离,求最大生成树。
思路
题目所给的数据是两两之间的连通关系,比较适合用kruskal+并查集求解。
但这个题要求的是最大生成树,排序的时候按降序排列或者将距离取反最后结果再取反就可以了。
代码
Source Code
Problem: 2377 User: liangrx06
Memory: 496K Time: 32MS
Language: C++ Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1000;
const int M = 20000;
struct Node {
int x, y, d;
};
int n, m;
Node node[M+1];
int pre[N+1];
int rank[N+1];
bool cmp(const Node& a, const Node& b)
{
return a.d < b.d;
}
void init()
{
for (int i = 1; i <= n; i ++) {
pre[i] = i;
rank[i] = 0;
}
}
int find(int a)
{
while (a != pre[a])
a = pre[a];
return a;
}
void unite(int a, int b)
{
a = find(a);
b = find(b);
if (a == b) return;
if (rank[a] < rank[b]) {
pre[a] = b;
} else {
pre[b] = a;
if (rank[a] == rank[b])
rank[a] ++;
}
}
bool same(int a, int b)
{
return find(a) == find(b);
}
int kruskal()
{
sort(node+1, node+m+1, cmp);
int res = 0;
int uniteTime = 0;
for (int i = 1; i <= m; i ++) {
int x = node[i].x, y = node[i].y;
if (!same(x, y)) {
unite(x, y);
uniteTime ++;
res += node[i].d;
}
}
if (uniteTime == n-1)
return -res;
else
return -1;
}
int main(void)
{
cin >> n >> m;
for (int i = 1; i <= m; i ++) {
scanf("%d%d%d", &node[i].x, &node[i].y, &node[i].d);
node[i].d *= -1;
}
init();
printf("%d\n", kruskal());
return 0;
}
POJ2395
http://poj.org/problem?id=2395
题意
有N个农场,它们是连通的,现在你要从1号农场找到路走到其他所有农场去。但是有个要求就是你必须使得你将要走的单段路的最大长度最小。也就是说,任意两个农场之间的路如果被你选中要走的话,那么这种单段路的最大值必须尽量小。
思路
其实就是要你选一些路使得所有农场属于同一个连通分量,且要求你输出最大边的值. 仔细想想kruskal算法,它从所有边长从小到大的顺序开始选边,每次选边都使得两个点连通. 可以证明kruskal算法所选择的最后一条边即是我们本题的答案.
最小瓶颈生成树在刘汝佳<<训练指南>>P343页有详细的介绍.
无向图G的一颗瓶颈生成树(bottleneck spanning tree)T是这样的一颗生成树,它最大的边权值在G的所有生成树中是最小的。
无向图的最小生成树一定是瓶颈生成树,但瓶颈生成树不一定是最小生成树。(来自百度百科:瓶颈生成树)
代码
Source Code
Problem: 2395 User: liangrx06
Memory: 388K Time: 79MS
Language: C++ Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 2000;
const int M = 10000;
struct Node {
int x, y, d;
};
int n, m;
Node node[M+1];
int pre[N+1];
int rank[N+1];
bool cmp(const Node& a, const Node& b)
{
return a.d < b.d;
}
void init()
{
for (int i = 1; i <= n; i ++) {
pre[i] = i;
rank[i] = 0;
}
}
int find(int a)
{
while (a != pre[a])
a = pre[a];
return a;
}
void unite(int a, int b)
{
a = find(a);
b = find(b);
if (a == b) return;
if (rank[a] < rank[b]) {
pre[a] = b;
} else {
pre[b] = a;
if (rank[a] == rank[b])
rank[a] ++;
}
}
bool same(int a, int b)
{
return find(a) == find(b);
}
int kruskal()
{
sort(node+1, node+m+1, cmp);
int res = 0;
int uniteTime = 0;
for (int i = 1; i <= m; i ++) {
int x = node[i].x, y = node[i].y;
if (!same(x, y)) {
unite(x, y);
uniteTime ++;
res = node[i].d;
}
}
if (uniteTime == n-1)
return res;
else
return -1;
}
int main(void)
{
cin >> n >> m;
for (int i = 1; i <= m; i ++) {
scanf("%d%d%d", &node[i].x, &node[i].y, &node[i].d);
}
init();
printf("%d\n", kruskal());
return 0;
}
AOJ2224
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=2224
题意
有N个木桩M个栅栏,栅栏连接木桩,现在这些栅栏围成的封闭空间里有至少一只猫,要求破环若干个栅栏救出猫,问破环栅栏的最小长度。
思路
题目要求栅栏不组成圈,又要求破坏栅栏的最小长度,那么剩下的栅栏自然是最大生成树了(当然前提是图是连通的)。kruskal+并查集求最大生成树,在从大到小添加边的过程中,如果发现新添加的边的两个节点已经连通,说明这个边需要去掉,累加这个边的长度到结果变量res。
另外,即使这个图非连通,这样的破圈求解过程仍然能够得到正确答案。
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 10000;
const int M = 10000*10000/2;
struct Node {
int x, y;
double d;
};
int n, m;
Node node[M+1];
int pre[N+1];
int rank[N+1];
bool cmp(const Node& a, const Node& b)
{
return a.d > b.d;
}
void init()
{
for (int i = 1; i <= n; i ++) {
pre[i] = i;
rank[i] = 0;
}
}
int find(int a)
{
while (a != pre[a])
a = pre[a];
return a;
}
void unite(int a, int b)
{
a = find(a);
b = find(b);
if (a == b) return;
if (rank[a] < rank[b]) {
pre[a] = b;
} else {
pre[b] = a;
if (rank[a] == rank[b])
rank[a] ++;
}
}
bool same(int a, int b)
{
return find(a) == find(b);
}
double distance(int x1, int y1, int x2, int y2)
{
return sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
}
double kruskal()
{
sort(node+1, node+m+1, cmp);
double res = 0;
for (int i = 1; i <= m; i ++) {
int x = node[i].x, y = node[i].y;
if (!same(x, y))
unite(x, y);
else
res += node[i].d;
}
return res;
}
int main(void)
{
int a[N+1], b[N+1];
cin >> n >> m;
for (int i = 1; i <= n; i ++) {
scanf("%d%d", &a[i], &b[i]);
}
for (int i = 1; i <= m; i ++) {
scanf("%d%d", &node[i].x, &node[i].y);
int x = node[i].x, y = node[i].y;
node[i].d = distance(a[x], b[x], a[y], b[y]);
}
init();
printf("%.3lf\n", kruskal());
return 0;
}
编程算法爱好者。