题目链接
Flatopia岛国完全平坦。不幸的是,Flatopia的公共高速公路系统非常糟糕。弗拉托利亚政府意识到了这个问题,并且已经建造了一些连接一些最重要城镇的高速公路。但是,仍有一些城镇无法通过高速公路抵达。有必要建造更多的高速公路,以便能够在不离开高速公路系统的情况下在任何一对城镇之间行驶。
Flatopian城镇的编号从1到N,城镇i的位置由笛卡尔坐标(xi,yi)给出。每条高速公路连接两个城镇。所有高速公路(原始高速公路和要建造的高速公路)都遵循直线,因此它们的长度等于城镇之间的笛卡尔距离。所有高速公路都可以在两个方向上使用。高速公路可以自由地相互交叉,但司机只能在位于两条高速公路尽头的小镇的高速公路之间切换。
Flatopian政府希望最大限度地降低建设新高速公路的成本。但是,他们希望保证每个城镇都可以从其他城镇到达公路。由于Flatopia是如此平坦,高速公路的成本总是与其长度成正比。因此,最便宜的高速公路系统将是最小化总公路长度的系统。
Sample Input
9
1 5
0 0
3 2
4 5
5 1
0 4
5 2
1 2
5 3
3
1 3
9 7
1 2
Sample Output
1 6
3 7
4 9
5 7
8 3
题意思路:N个点,给你N个点的坐标,现在还有Q条边已经连接好了。问把N个点怎么连接起来的花费的距离最短?
坑点:注意多组输入输出可能会超时。本题为:Special judgeYes,只要是符合要求的正确答案即可。prim或者kruskal的路径输出。
①prim,已经建好的边的权值设置为0。开一个pre数组//pre[i]记录距离i最近的点。那么建立的边也就为pre[i]和i,但是i节点不能是之前已经建立好的边,也就是dis[i]不能为0。
②kruskal,在输入的建好的边时候就把这两条边合并,然后统计建好了多少条边,还需要建立多少条边。
比如有4个点1,2,3,4,已经建好边有3条
1 2
2 1
3 1
此时还需要建立一条边,而不是还需要建立4 - 1 - 3 = 0条边 。
一:prim解法:
/*prim算法:已经建好的边的权值设置为0。开一个pre数组//pre[i]记录距离i最近的点。
那么建立的边也就为pre[i]和i,但是i节点不能是之前已经建立好的边,也就是dis[i]不能为0。*/
//本题就比普通的prim的算法多开了一个pre数组
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
using namespace std;
const int maxn = 755;
const int inf = 0x3f3f3f3f;
int n, m, mp[maxn][maxn];
struct Point
{
int x, y;
}point[maxn];
void prim()
{
bool vis[maxn];
int dis[maxn], pre[maxn];//pre[i]记录距离i最近的点
//memset(vis, 0, sizeof(vis));
for (int i = 1; i <= n; i++)
{
dis[i] = mp[1][i];
vis[i] = false;//此行与21行memset数组的效果一样
pre[i] = 1;//pre数组一开始存储的全是1
}
//vis[1] = true; //此行注释掉会错,
//for (int i = 1; i < n; i++)//因为:此行是n-1次循环,若要注释掉vis[1] = true; 则循环要改为for (int i = 1; i <= n; i++)
for (int i = 1; i <= n; i++)
{
int mi = inf, v;
for (int j = 1; j <= n; j++)
{
if (!vis[j] && dis[j] < mi)
{
mi = dis[j];
v = j;
}
}
if(dis[v] != 0) printf("%d %d\n",pre[v], v);//不是已经建好的路就输出当前建立的边
vis[v] = true;
for (int j = 1; j <= n; j++)
{
if (!vis[j] && dis[j] > mp[v][j])
{
dis[j] = mp[v][j];
pre[j] = v;
}
}
}
}
int main()
{
scanf("%d", &n);//多组会wa
{
for (int i = 1; i <= n; i++)//从1到n依次加入点
{
scanf("%d%d", &point[i].x, &point[i].y);
mp[i][i] = 0;//自己到自己的距离初始化为0
for (int j = 1; j < i; j++)//新加入的点到所有已加入的点的距离存入mp
{
mp[i][j] = mp[j][i] = (point[i].x - point[j].x) * (point[i].x - point[j].x) +
(point[i].y - point[j].y) * (point[i].y - point[j].y);
}
}
int Q;
scanf("%d", &Q);//Q条路已经被修建
while (Q--)
{
int a, b;
scanf("%d%d", &a, &b);//点a到点b的路已经存在,
mp[a][b] = mp[b][a] = 0;//不用修建,存为0
}
prim();
}
return 0;
}
二:krusal解法:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
using namespace std;
const int maxn = 755;
const int INF = 0x3f3f3f3f;
int n, m, cnt, f[maxn];
struct Point
{
int x, y;
}point[maxn];
struct Edge
{
int u, v, w;
bool operator < (const Edge &A) const//重载<操作符。可以对两个Edge类型数据使用 < 操作符进行比较
//括号中的const表示参数A对象不会被修改,最后的const表明调用函数对象不会被修改!
{
return w < A.w;
}
}edge[maxn * maxn];
int Find(int x)//查找(x)根节点
{
//return f[x] == x ? x : f[x] = Find(f[x]);//与26 27 28行代码功能一样
int r=x; //委托 r 去找(根节点)
while(f[r]!=r) //如果r的上级(父节点)不是r自己(也就是说找到的不是根节点)
r=f[r];// r 就接着找他的上级,直到找到根节点为止。
return r; //(返回根节点)
}
bool Union(int a, int b)//我想让虚竹和周芷若做朋友// //判断a b是否连通
{ //如果已经连通,就不用管了 如果不连通,就把它们所在的连通分支合并起,
int root1 = Find(a), root2 = Find(b);//虚竹的老大是玄慈,芷若MM的老大是灭绝
if (root1 != root2) //玄慈和灭绝显然不是同一个人
{
f[root1] = root2; //方丈只好委委屈屈地当了师太的手下啦
return true;
}
return false;
}
void kruskal()
{
for (int i = 0; i < m; i++)
{
int u = edge[i].u, v = edge[i].v, w = edge[i].w;
if (Union(u, v)==true)
{
printf("%d %d\n", u, v);
cnt++;
if (cnt == n -1 ) break;
}
}
}
int main()
{
scanf("%d", &n);//多组会wa
{
m = 0;
for (int i = 1; i <= n; i++)
{
scanf("%d%d", &point[i].x, &point[i].y);
for (int j = 1; j < i; j++)
{
edge[m].u = i;
edge[m].v = j;
edge[m++].w = (point[i].x - point[j].x) * (point[i].x - point[j].x) +
(point[i].y - point[j].y) * (point[i].y - point[j].y);
}
}
cnt = 0;//建好了0条边
for (int i = 0; i <= n; i++)
f[i] = i;//并查集初始化
sort(edge, edge + m);//排序
int Q;
scanf("%d", &Q);
while (Q--)
{
int a, b;
scanf("%d%d", &a, &b);
if(Union(a, b)==true) cnt++;
}
kruskal();
}
return 0;
}