acwing2681
在一个热带雨林中生存着一群猴子,它们以树上的果子为生。
昨天下了一场大雨,现在雨过天晴,但整个雨林的地表还是被大水淹没着,部分植物的树冠露在水面上。
猴子不会游泳,但跳跃能力比较强,它们仍然可以在露出水面的不同树冠上来回穿梭,以找到喜欢吃的果实。
现在,在这个地区露出水面的有 N 棵树,假设每棵树本身的直径都很小,可以忽略不计。
我们在这块区域上建立直角坐标系,则每一棵树的位置由其所对应的坐标表示(任意两棵树的坐标都不相同)。
在这个地区住着的猴子有 M 个,下雨时,它们都躲到了茂密高大的树冠中,没有被大水冲走。
由于各个猴子的年龄不同、身体素质不同,它们跳跃的能力不同。
有的猴子跳跃的距离比较远(当然也可以跳到较近的树上),而有些猴子跳跃的距离就比较近。
这些猴子非常聪明,它们通过目测就可以准确地判断出自己能否跳到对面的树上。
现已知猴子的数量及每一个猴子的最大跳跃距离,还知道露出水面的每一棵树的坐标,你的任务是统计有多少个猴子可以在这个地区露出水面的所有树冠上觅食。
输入格式
第 1 行为一个整数,表示猴子的个数 M;
第 2 行为 M 个整数,依次表示猴子的最大跳跃距离(每个整数值在 1∼1000 之间);
第 3 行为一个整数表示树的总棵数 N;
第 4 行至第 N+3 行为 N 棵树的坐标(横纵坐标均为整数,范围为:−1000∼1000)。
同一行的整数间用(若干)空格分开。
输出格式
包括一个整数,表示可以在这个地区的所有树冠上觅食的猴子数。
数据范围
1≤M≤500,
2≤N≤1000
基本的思路是求出一棵MST,然后获取MST中最长的边,跳跃能力大于这个长度的猴子就可以在所有树冠上觅食。
题目给出了坐标,我们可以得到一个完全图,然后求这个完全图的MST。理论上,这是一个稠密图,所以应该使用prim算法。但是acwing的标签是kruskal(笑的),那就把两种方法都实现以下看看区别:
kruskal版:
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
//用于kruskal
struct edge {
	int u;
	int v;
	double w;
	edge(int x,int y,double z):u(x),v(y),w(z){}
	edge(){}
};
//树的坐标
struct tree {
	int x;
	int y;
	tree(int _x,int _y):x(_x),y(_y){}
	tree() { x = y = 0; }
};
const int MMAX = 505;
const int NMAX = 1005;
const double INF = 10000;
int M, N;
//完全图中的所有边
vector<edge> edges;
//猴子的跳跃距离
int jmp[MMAX] = { 0 };
//树的坐标
tree pos[NMAX];
//储存MST中最长的边的长度
double maxlen = 0;
//并查集
int uf[NMAX];
void input() {
	cin >> M;
	for (int i = 0; i < M; i++)
		cin >> jmp[i];
	cin >> N;
	for (int i = 0; i < N; i++) {
		int x, y;
		cin >> x >> y;
		pos[i] = tree(x, y);
	}
}
//预处理
void cal() {
	int x1, y1, x2, y2;
	double w;
	for (int i = 0; i < N; i++) {
		x1 = pos[i].x;
		y1 = pos[i].y;
		for (int j = 0; j < i; j++) {
			x2 = pos[j].x;
			y2 = pos[j].y;			
			w= sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2));
			edges.push_back(edge(i, j, w));
		}
	}
}
//若a,b不连通,就将其所属两个集合union
void unionfind(int a, int b) {
	while (a != uf[a])		a = uf[a];
	while (b != uf[b])		b = uf[b];
	if (a != b)	uf[b] = a;
}
bool connect(int a, int b) {
	while (a != uf[a])		a = uf[a];
	while (b != uf[b])		b = uf[b];
	return a == b;
}
bool cmp(edge a,edge b) {
	return a.w < b.w;
}
//使用kruskal算法求解
void kruskal() {
	//初始化并查集
	for (int i = 0; i < N; i++)
		uf[i] = i;
	//将所有边升序排序
	sort(edges.begin(), edges.end(), cmp);
	//已加入MST的边数
	int count = 0;
	for (int i = 0; i < edges.size(); i++) {
		//先判断边的两个节点是否已经在同一个集合中
		int u = edges[i].u;
		int v = edges[i].v;
		if (connect(u, v))
			continue;
		unionfind(u, v);
		double w = edges[i].w;
		count++;
		if (w > maxlen)		maxlen = w;
		if (count == N - 1)	break;
	}
}
int main(void) {
	input();
	cal();
	kruskal();
	int count = 0;
	for (int i = 0; i < M; i++) {
		if (jmp[i] >= maxlen) {
			count++;
		}			
	}
	cout << count << endl;
	return 0;
}
算法将完全图中的所有边升序排序(用stl的sort函数),然后配合并查集,注意取出最小的边加入到MST中。
prim版:
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
//树的坐标
struct tree {
	int x;
	int y;
	tree(int _x, int _y) :x(_x), y(_y) {}
	tree() { x = y = 0; }
};
const int MMAX = 505;
const int NMAX = 1005;
const double INF = 10000;
int M, N;
//邻接矩阵
double G[NMAX][NMAX] = { INF };
//判别该点是否已加入到MST中
bool visit[NMAX] = { false };
//每个点到MST的当前最短距离
double disto[NMAX] = { INF };
//猴子的跳跃距离
int jmp[MMAX] = { 0 };
//树的坐标
tree pos[NMAX];
//储存MST中最长的边的长度
double maxlen = 0;
void input() {
	cin >> M;
	for (int i = 0; i < M; i++)
		cin >> jmp[i];
	cin >> N;
	for (int i = 0; i < N; i++) {
		int x, y;
		cin >> x >> y;
		pos[i] = tree(x, y);
	}
}
void cal() {
	int x1, y1, x2, y2;
	double w;
	for (int i = 0; i < N; i++) {
		x1 = pos[i].x;
		y1 = pos[i].y;
		for (int j = 0; j <= i; j++) {
			x2 = pos[j].x;
			y2 = pos[j].y;
			w = sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2));
			G[i][j] = G[j][i] = w;
		}
	}
}
//用prim算法求解MST
void prim() {
	disto[0] = 0;
	
	//新加入MST的节点
	int cid = 0;
	//已加入MST的节点数
	int count = 0;
	while (true) {
		visit[cid] = true;
		count++;
		if (count == N)	break;
		//松弛节点
		for (int j = 0; j < N; j++) {
			if (visit[j])	continue;
			if (disto[j] > G[cid][j]) {
				disto[j] = G[cid][j];
			}
		}
		//找到与树距离最近的节点
		double temp = INF;
		int id;
		for (int i= 0; i < N; i++) {
			if (visit[i])	continue;
			if (disto[i] < temp) {
				id = i;
				temp = disto[id];
			}
		}
		//更新cid和maxlen
		cid = id;
		if (maxlen < temp)	maxlen = temp;
	}
}
int main(void) {
	fill(disto, disto + NMAX, INF);
	input();
	cal();
	prim();
	int count = 0;
	for (int i = 0; i < M; i++) {
		if (jmp[i] >= maxlen) {
			count++;
		}
	}
	cout << count << endl;
	return 0;
}
由于是稠密图,所以使用了邻接矩阵来存储图。使用disTo数组,储存节点到当前生成树的最短距离。prim的核心思想是找到与当前MST距离最小的点将其加入,根据算法第四版,我们使用desire策略,即每加入一个点,就松弛该点(判断该节点u所邻接的节点v的disTo值是否可以经由u变得更小)。
结果分析:理论上,prim的复杂度取决于节点的数量,kruskal的复杂度取决于边的数量,对于完全图这样的稠密图,prim的性能要更好,而OJ的结果也能反映出这个特点:

第一个是prim,第二个是kruskal

 
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号