分治法:棋盘覆盖+最大子段和+最近点对

棋盘覆盖

看到其他博客写的那么好
我觉得简单说一下思路就好(就是懒bushi:

我们从4* 4的棋盘看
将4* 4棋盘分成4份,每份都是2* 2(4个格子),任意一个格子为特殊点,那么剩下三个点自然而然就是L型骨牌的排放地,那么当前这四个格子就排满了。要想让剩下三份2* 2棋盘能被L型骨牌排满就让每一份2* 2棋盘都有一个特殊点,并且这三个特殊点还得连一块,所有最好的办法就是把除了已经有特殊点的剩下三份2* 2棋盘的相连点设为特殊点(划分依据)

比如
在这里插入图片描述
然后就是代码实现:
(tr,tc)是左上角点坐标(行,列),(dr,dc)是特殊点坐标(行,列),size是棋盘规格(size*size)

详细注释写在代码里了。

这边有一个需要注意的,就是如何填充L型骨牌,我用的是数字进行填充,一开始我是只设了chess全局变量,发现有问题,因为是将大的拆成小的,拆到不能拆为止,每次拆都要进入一个新的chessBoard函数(递归),chess都是++,对于新的棋盘没问题,每新一次拆分填充的颜色自然不同,但是当递归回溯,因为chess是全局变量,那么此时的chess就与开始递归时的chess不同了。

然后我就像把chess作为参数传入,也不行,对于不同子块填充L型数字是不同的,如果chess作为参数参与递归,那么当前棋盘进行划分,每一块新的棋盘的填充数字都是在当前棋盘的chess的基础上++,一个44的棋盘递归进入的所有22的棋盘的新的特殊点都是chess+1,错误。

所有我们既要做到每进入一个新的棋盘chess都要++,并且回溯后的chess还和开始递归的chess一样,那么就设一个变量nowchess存储开始回溯的chess,当前棋盘设置的L型骨牌都由nowchess填充。

#include<iostream>
using namespace std;
int board[20][20];
int chess = 0;
void chessBoard(int tr, int tc, int dr, int dc, int size) {
    
    
	//左上角点坐标(行,列),特殊点坐标(行,列),棋盘规格size*size
	if (size == 1)//当前规格为1*1,不能再划分了
		return;
	int len = size / 2;//行一分为2
	chess++;
	int nowchess = chess;//这一步处理重要!!
	if (dr < tr + len && dc < tc + len)//特殊点在棋盘左上方
		chessBoard(tr, tc, dr, dc, len);
	else {
    
    //特殊点不在左上,左上方的右下角必作为新的特殊点
		board[tr + len - 1][tc + len - 1] = nowchess;
		chessBoard(tr, tc, tr + len - 1, tc + len - 1, len);
	}
	if (dr < tr + len && dc >= tc + len)//特殊点在棋盘右上方
		chessBoard(tr, tc + len, dr, dc, len);
	else {
    
    //特殊点不在右上,右上方的左下角必作为新的特殊点
		board[tr + len - 1][tc + len] = nowchess;
		chessBoard(tr, tc + len, tr + len - 1, tc + len, len);
	}
	if (dr >= tr + len && dc < tc + len)//特殊点在棋盘左下方
		chessBoard(tr + len, tc, dr, dc, len);
	else {
    
    //特殊点不在左下,左下方的右上角必作为新的特殊点
		board[tr + len][tc + len - 1] = nowchess;
		chessBoard(tr + len, tc, tr + len, tc + len - 1, len);
	}
	if (dr >= tr + len && dc >= tc + len)//特殊点在棋盘右下方
		chessBoard(tr + len, tc + len, dr, dc, len);
	else {
    
    //特殊点不在右下,右下方的左上角必作为新的特殊点
		board[tr + len][tc + len] = nowchess;
		chessBoard(tr + len, tc + len, tr + len, tc + len, len);
	}
		
}
int main()
{
    
    
	int size = 4;//棋盘规格为size*size
	chessBoard(0, 0, 0, 0, size);
	for (int i = 0; i < size; i++) {
    
    
		for (int j = 0; j < size; j++)
			printf("%2d ", board[i][j]);
		cout << endl << endl;
	}

	return 0;
}

最大子段和

#include<iostream>
using namespace std;
int maxsum(int str[], int beg, int end);//分治
int maxsum2(int str[], int beg, int end);//在线算法
int main()
{
    
    
	//采用分治的思想,时间复杂度o(nlogn),在线算法是o(n)
	int str[] = {
    
     -4,-5,-3,1,-10,-4,-5,-2 };
	cout << maxsum(str, 0, 8) << endl;
	cout << maxsum2(str, 0, 8);
	return 0;
}
int maxsum2(int str[], int beg, int end) {
    
    
	int sum = -0x3f3f3f3f, temp = 0;
	for (int i = beg; i <= end; i++) {
    
    
		temp += str[i];
		sum = max(sum, temp);
		if (temp < 0) temp = 0;
	}
	return sum;
}
int maxsum(int str[], int beg, int end)
{
    
    
	if (beg == end) return str[beg];
	//分治的本质也是递归,但凡递归一定要保证有递归结束条件
	int mid = (beg + end) >> 1;
	int sum1 = maxsum(str, beg, mid);
	int sum2 = maxsum(str, mid + 1, end);

	int tempsum = 0;
	int leftsum = -0x3f3f3f3f;
	//下面两个循环求得是穿过分界点的最大子序列和
	for (int i = mid; i >= beg; i--)
	{
    
    
		tempsum += str[i];
		leftsum = max(leftsum, tempsum);
	}
	tempsum = 0;
	int rightsum = -0x3f3f3f3f;
	for (int i = mid+1; i <= end; i++)
	{
    
    
		tempsum += str[i];
		rightsum = max(rightsum, tempsum);
	}

	return max(leftsum + rightsum, max(sum1, sum2));
}

最近点对

(发布文章一直出错,原因竟然我标题里有最,我在线无语…
先用zui代替发出去再修改成最保存
麻了麻了真的麻了)
我觉得网上的解析写的挺好的,不想写了(hh就是懒
放个代码吧
因为没找到题目进行测试,所以不保证完全没问题,反正先扔着,以后有机会再修改(一般是没机会,估计我早忘了
没找到什么特别短的代码,以后有看到再修改吧(同上

理个简单的思路:
一块区间的所有点根据x进行排序,将所有点集分成两半,平分线x=m,保证两片区域的点尽可能相等
分别求两区间的最近点对距离d1,d2,d=min(d1,d2)
然后从刚刚的平分线x=m,往左到x=m-d,往右到x=m+d这一块区间[m-d,m+d]再找最近点对的距离d3,将d与d3进行比较,最小的那个距离就是我们想要的
采用的分治和递归(分治一般都递归
递归的返回条件(能计算当前区间的最近点对
两种情况:
1.当前区间只剩两个点,high-low==1,返回两点距离
2.当前区间三个点,a,b,c,那么最近点要么a-b,要么b-c,返回min(a-b,b-c)

#include<iostream>
#include<utility>
#include<queue>
#include<cmath>
#include<vector>
using namespace std;
typedef pair<double, double>PII;
double Closet(int low,int high,vector<PII>point);
double distance(int a,int b,vector<PII>p);
bool cmpy(PII a, PII b) {
    
    
	return a.second < b.second;
}
bool cmpx(PII a, PII b) {
    
    
	return a.first < b.first;
}
int main()
{
    
    
	int n;
	cin >> n;
	vector<PII>point;
	for (int i = 0; i < n; i++)//载入二维平面点的集合
	{
    
    
		double x, y;
		cin >> x >> y;
		point.push_back({
    
     x,y });
	}
	//默认是根据x按顺序输入,如果不按顺序可以加个sort先排序
	sort(point.begin(), point.end(), cmpx);
	double d = Closet(0,point.size()-1,point);
	cout << d;
	return 0;
}
double Closet(int low,int high,vector<PII>point) {
    
    
	double d1, d2, d3, d;
	//low对于最左边点的左边,high对应最右边点的坐标
	if (high - low == 1)
		//只剩两个点了,这两个点的距离只能是当前区间的最近点距离
		return distance(low,high,point);
	else if (high - low == 2)
		//三个点求最近对距离
	{
    
    
		d1 = distance(low, low + 1, point);
		d2 = distance(low + 1, high, point);
		//d3 = distance(low, high, point);
		//不明白书上给的代码为什么还要比较最左边和最右边的,离谱
		return min(d1,d2);
	}
	int mid = (high + low) / 2;//将一整片区间平分成两份
	d1 = Closet(low, mid,point);
	d2 = Closet(mid + 1, high,point);
	d = min(d1, d2);
	//算跨区间的点的最近对
	vector<PII>span;
	for (int i = mid; i >= low && point[mid].first - point[i].first <= d; i--)
		span.push_back({
    
     point[i].first,point[i].second });
	for (int i = mid+1; i <= high && point[i].first - point[mid].first <= d; i++)
	//这边注意一些i是从mid+1开始的,刚开始没注意就出问题了
		span.push_back({
    
     point[i].first,point[i].second });
	//按照y进行排序
	sort(span.begin(), span.end(), cmpy);
	for(int i=0;i<span.size();i++)
		for (int j = i + 1; j < span.size() && span[j].second - span[i].second <= d; j++) {
    
    
			double t = distance(i, j, span);
			d = min(d, t);
		}
	return d;

}
double distance(int a,int b,vector<PII>p) {
    
    
	return sqrt(pow(p[a].first - p[b].first, 2) + pow(p[a].second - p[b].second, 2));
}

猜你喜欢

转载自blog.csdn.net/weixin_50816938/article/details/121056987