【数据结构与算法设计-基础向】C/C++编程练习 - 套圈(分治法解决最近对问题)

 

题目: 

Have you ever played quoit in a playground? Quoit is a game in which flat rings are pitched at some toys, with all the toys encircled awarded. In the field of Cyberground, the position of each toy is fixed, and the ring is carefully designed so it can only encircle one toy at a time. On the other hand, to make the game look more attractive, the ring is designed to have the largest radius. Given a configuration of the field, you are supposed to find the radius of such a ring. Assume that all the toys are points on a plane. A point is encircled by the ring if the distance between the point and the center of the ring is strictly less than the radius of the ring. If two toys are placed at the same point, the radius of the ring is considered to be 0.

Input The input consists of several test cases. For each case, the first line contains an integer N (2 <= N <= 100,000), the total number of toys in the field. Then N lines follow, each contains a pair of (x, y) which are the coordinates of a toy. The input is terminated by N = 0.

Output For each test case, print in one line the radius of the ring required by the Cyberground manager, accurate up to 2 decimal places.

测试用例:

输入:                                                        输出:

4                                                                 1.12

0 3

3 2

4 0

7 1

0

长话短说,题目的大概意思就是输入N个坐标点,要求你求这些点中最短的距离,其中如果有重合的点,就输出0。需要注意的是,题目有多组用例,当输入0时表示输入结束。

如果博主没有猜错,那么看这篇文章的你应该是北理工的学生吧。如果猜错的话就当我没说。

那么进入正题,这其实是一个典型的分治法求最小对的问题,不过这道题对时间的要求比较严格,需要你尽可能的去做优化。

如果你没有彻底掌握分治法求最小对的问题,那么耐心看完这篇文章,相信你会有所收获;如果你是TLE来找优化方法的,相信你看完也能找到答案。

接下来博主将逐段讲解代码,以便掌握该类问题的求解方法。

(因为博主也是学生,因此在讲解时可能会有些啰嗦;但如果你需要学习分治法,那么讲解详细一点也并不是一件坏事)

简要思路

首先此题不能使用蛮力法,明显会TLE。因此我们使用分治法来解决。

解题思路是,先把坐标按照x升序(从小到大)排列,并找到x的中位数,来分为两部分。

对每一部分继续划分,递归处理,直到坐标数到达1或2,return结果,对两部分的结果取最小值,作为宽度d。

以上不能处理最小值的点恰好在分界线两侧的情况。

因此我们要再对分界线上的点进行处理,找一个宽度d,在分界线两侧形成一个总宽度为2d的宽度带,并对宽度带中的点进行遍历计算,更新最小距离值。

具体实现:

一、主函数部分

首先来看我们的主函数部分,这边其实没有什么好说的,值得注意的就是要使用double类型来储存坐标数据,如果你使用float类型,将会因精度不够而WA。

然后,数据的存储方式可以采用自定义的结构体,也可以像博主一样直接使用pair类型的vector进行储存,后者相对来说比较方便,可以不用指定数组的大小。

注意:如果你想使用map来解决这道题,注意要使用multimap;如果单纯使用map并以x作为key值,可能会因为有重复的x坐标而造成数据的丢失。

接下来为了后面的分治,我们需要将坐标按照x的大小进行排序,这里要写一个针对x的比较函数,虽然比较简单,但后面依然会把代码展示并做略微讲解。

最后的clear是为了下一轮的数据处理。

#include<vector>
#include<utility>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstdbool>
    using namespace std;
vector <pair<double, double>> P;        //定义pair类型的vector

int main() {
		int n, i;
		double dis, x, y;
		while (1) {

			scanf("%d", &n);
			if (n == 0) {
				break;        //输入0结束
			}

			i = n;
			while (i--) {
				scanf("%lf %lf", &x, &y);
				P.push_back(pair<double, double>(x, y));
			}

			sort(P.begin(), P.end(), cmp1);        //预备工作,将坐标按x排序

			dis = shortestdis(0, n - 1);        //核心步骤,找最小对

			dis = sqrt(dis) / 2;        //因为题目要求半径,因此要除以二
			printf("%.2lf\n", dis);

			P.clear();        //清空vector,以便进行下一组数据的输入
		}
		return 0;
	}

二、非递归的其他函数部分

这一部分有三个函数。

前两个都是比较函数,以bool为类型,static可加可不加,const也可以不加。

优化:下面一个是计算距离的函数,加inline是为了提高一点点效率。另外值得一提的是,这里没有对距离进行sqrt运算,原因之一是为了保证精度;原因之二是开平方在计算机内部并不是一件消耗低的事情,因此我们要尽量减少使用平方根。

static bool cmp1(const pair<int, int>& a, const pair<int, int>& b) {
		return a.first < b.first;        //比较函数1,比x
	}
static bool cmp2(const pair<int, int>& a, const pair<int, int>& b) {
		return a.second < b.second;        //比较函数2,比y
	}
inline double calculatee(pair<double,double>a, pair<double, double>b) {
		return ((a.first - b.first) * (a.first - b.first) + (a.second - b.second) * (a.second - b.second));        //计算距离的函数
	}

三、递归函数部分

这一部分是该程序的核心。

首先我们根据left和right来确定数组的元素个数,如果是1,那么无法计算距离,我们返回极大值;如果是2,我们借助上面的计算函数来确定距离的平方值。

接下来,如果元素个数大于2,首先我们获取中间元素,利用(left + right) / 2来取得中间元素的下标。

然后将左右两侧递归,进行分治。

优化:接下来获取左右两侧最小平方距离的最小值mind,这里我们获取以后开一次根号得到sqmd,以便后面进行比较,否则跟距离的平方(即mind比较的值还要进行平方运算,这样造成的资源消耗会得不偿失。

然后我们以上面的中位元素做基准,将它左右sqmd距离的元素纳入临时数组中。

然后对该临时数组按y排序,原因是我们按y排序以后,后面进行遍历时可以大大提高效率。在下文中我们详细讨论。

接下来就是遍历,我们先取临时数组里面的第一个元素(暂时叫a),然后将它和它后面的元素(暂时叫bi)逐一求距离。

优化:这里注意while循环的第二个条件,它求的是abi的y方向距离,我们知道,经过前面的y排序,bi的y值是非递减的。举个例子,也就是说b3的y值一定大于等于b2的y值的。这样,当我们在进行while循环时,发现某一bia的y方向距离已经大于等于我们的sqmd了,那也没有必要计算bia的x方向与y方向的总距离了;同时,对于bi后面的所有元素,它们与a的y方向的距离一定不会比bia的y方向的距离小,那么我们就可以不必继续进行while循环,以此来提高效率。

double shortestdis(int left, int right) {

		int s = right - left + 1;        //s是元素的个数
        
        //这里是对于元素个数为2或1的情况
		if (s == 2) {
				double disone;
				disone = calculatee(P[left], P[right]);
				return disone;
		}
		if(s==1) {
				return 0x3f3f3f3f;
		}

        //这里是其他情况
		vector <pair<double, double>> SPV;        //创建一个临时数组

		int i, j, k, capav;
		double d1, d2, mind, sqmd, spx;

        //对数组分治,递归
		k = (left + right) / 2;
		d1 = shortestdis(left, k);
		d2 = shortestdis(k + 1, right);
        
        //取上面二者最小,并开根号
		mind = min(d1, d2);
		sqmd = sqrt(mind);

        //处理分界线两端的元素,只需要处理分界线左右sqmd距离的元素即可
		spx = P[k].first;        //以分界线的x坐标为基准

        //将元素存入临时数组
		for (i = left; i <= right; i++) {
			if (abs(P[i].first - spx) < sqmd) {
				SPV.push_back(P[i]);
			}
		}

        //临时数组按y排序
		sort(SPV.begin(), SPV.end(), cmp2);

		capav = SPV.size();        //获得临时数组的大小

        //遍历分界线附近的元素,查看有无更小的距离
		for (i = 0; i < capav - 1; i++) {
			j = i + 1;
			while ((j < capav) && ((SPV[j].second - SPV[i].second) < sqmd)) {        //遍历继续的条件是j要小于总元素个数,并且y方向的距离要小于最小距离
				mind = min(((SPV[j].first - SPV[i].first) * (SPV[j].first - SPV[i].first) + (SPV[j].second - SPV[i].second) * (SPV[j].second - SPV[i].second)), mind);        //判断是否更小
				j++;
			}
		}

		return mind;        //返回最小值
	}

完整代码

不含注释。

#include<vector>
#include<utility>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstdbool>
	using namespace std;
	vector <pair<double, double>> P;
	static bool cmp1(const pair<int, int>& a, const pair<int, int>& b) {
		return a.first < b.first;
	}
	static bool cmp2(const pair<int, int>& a, const pair<int, int>& b) {
		return a.second < b.second;
	}
	inline double calculatee(pair<double,double>a, pair<double, double>b) {
		return ((a.first - b.first) * (a.first - b.first) + (a.second - b.second) * (a.second - b.second));
	}
	double shortestdis(int left, int right) {
		int s = right - left + 1;
		if (s == 2) {
				double disone;
				disone = calculatee(P[left], P[right]);
				return disone;
		}
		if(s==1) {
				return 0x3f3f3f3f;
		}
		vector <pair<double, double>> SPV;
		int i, j, k, capav;
		double d1, d2, mind, sqmd, spx;
		k = (left + right) / 2;
		d1 = shortestdis(left, k);
		d2 = shortestdis(k + 1, right);
		mind = min(d1, d2);
		sqmd = sqrt(mind);
		spx = P[k].first;
		for (i = left; i <= right; i++) {
			if (abs(P[i].first - spx) < sqmd) {
				SPV.push_back(P[i]);
			}
		}
		sort(SPV.begin(), SPV.end(), cmp2);
		capav = SPV.size();
		for (i = 0; i < capav - 1; i++) {
			j = i + 1;
			while ((j < capav) && ((SPV[j].second - SPV[i].second) < sqmd)) {
				mind = min(((SPV[j].first - SPV[i].first) * (SPV[j].first - SPV[i].first) + (SPV[j].second - SPV[i].second) * (SPV[j].second - SPV[i].second)), mind);
				j++;
			}
		}
		return mind;
	}
	int main() {
		int n, i;
		double dis, x, y;
		while (1) {
			scanf("%d", &n);
			if (n == 0) {
				break;
			}
			i = n;
			while (i--) {
				scanf("%lf %lf", &x, &y);
				P.push_back(pair<double, double>(x, y));
			}
			sort(P.begin(), P.end(), cmp1);
			dis = shortestdis(0, n - 1);
			dis = sqrt(dis) / 2;
			printf("%.2lf\n", dis);
			P.clear();
		}
		return 0;
	}

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
最大子段和问题是指在一个数列中找到一个子序列,使得该子序列中所有元素的和最大。以下是三种常见的算法实现: 1. 蛮力法 蛮力法是最朴素的解法,它的时间复杂度为 $O(n^2)$。具体实现如下: ```c++ int maxSubArray(int nums[], int n) { int ans = INT_MIN; for (int i = 0; i < n; i++) { int sum = 0; for (int j = i; j < n; j++) { sum += nums[j]; ans = max(ans, sum); } } return ans; } ``` 2. 分治法 分治法的时间复杂度为 $O(n\log n)$,它将问题分成三个部分:求解左半部分的最大子段和、求解右半部分的最大子段和、求解跨越中点的最大子段和。具体实现如下: ```c++ int maxSubArray(int nums[], int left, int right) { if (left == right) return nums[left]; int mid = left + (right - left) / 2; int leftMax = maxSubArray(nums, left, mid); int rightMax = maxSubArray(nums, mid + 1, right); int crossMax = nums[mid]; int sum = nums[mid]; for (int i = mid - 1; i >= left; i--) { sum += nums[i]; crossMax = max(crossMax, sum); } sum = crossMax; for (int i = mid + 1; i <= right; i++) { sum += nums[i]; crossMax = max(crossMax, sum); } return max(leftMax, max(rightMax, crossMax)); } ``` 3. 动态规划动态规划法的时间复杂度为 $O(n)$,它定义了一个状态数组 $dp$,其中 $dp[i]$ 表示以 $i$ 结尾的最大子段和。具体实现如下: ```c++ int maxSubArray(int nums[], int n) { int dp[n]; dp[0] = nums[0]; int ans = nums[0]; for (int i = 1; i < n; i++) { dp[i] = max(dp[i - 1] + nums[i], nums[i]); ans = max(ans, dp[i]); } return ans; } ``` 以上是三种常见的算法实现,需要注意的是,在实际应用中,我们还可以使用其他优化方法,如前缀和、后缀和、单调栈等,以进一步提高算法效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

很多时候不懂事_7295

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值