SWUST OJ 249: 凸包面積(分治法求凸包面積)

好蠢哦,很多細節錯誤,搞得wa好幾次

題目描述

原題鏈接點我
在這裏插入圖片描述

總結一下,就是簡單的分治法求凸包面積

思路

如何使用分治法求凸包面積

分治法:將問題劃分爲同一類型的若干個子問題,問題足夠小時求解,最後合併結果

首先,我們將點集劃分爲兩個部分,然後逐步求解

如何劃分
爲了最好的劃分效果,我們希望找到兩個點,能劃分點集,同時是凸包的定點。
我們選擇水平方向上最兩邊的點,作爲第一次劃分的依據
在這裏插入圖片描述

第二步求子集的解

暫時只考慮上半部分(因爲下半部分可以相同方法解決)
我們希望能找到一個點,能劃分子集,同時是頂點,最能肯定的一件事是
距離第一次劃分的邊最遠的點一定是我們需要的點

在這裏插入圖片描述
如何計算
考慮到距離涉及開方、除法等操作,我們可以利用三角形的面積,
如下圖,找到面積最大的點即可,
面積怎麼算?
我們在線性代數中,得到過這個問題的答案,給出三點座標,得到面積

通過三個點的座標求出三角形面積的公式 當三個點A、B、C的座標分別爲A(x1,y1)、B(x2,y2)、C(x3、y3)時,三角形面積爲, S=(x1y2-x1y3+x2y3-x2y1+x3y1-x2y2)。

在這裏插入圖片描述

下一步劃分

觀察上圖,我們發現:三角形內部的點我們是不用考慮的,可以假設當前連線就是三角形的邊,你就能得到上面的結論,
於是我們再次縮小
在這裏插入圖片描述
現在,只需要仿照前一步就能得到我們要的頂點。

如何解決SWUST OJ 249?

注意到題中需要的是面積,我們稍加觀察就能發現:
上半部分的面積可以通過三部分的三角形面積相加得到!!
這三部分分別是左端點,右端點,最高點以及由他們劃分出來的子三角形構成,
你只需要將計算得來的面積保留下來,在最後一步合併,就做到了
(另外,上述求三角形面積的公式中,設計除2,這將帶來小數,所以我們可以在最後對所有的結果和相加後再除2,這樣避免了因爲精度導致問題的可能
在這裏插入圖片描述代碼如下:
C++11去掉abs函數,並將創建對象修改爲{}初始化即可

#include<iostream>
#include <iomanip>  //不要忘了頭文件
using namespace std;
//點類
int abs(int num) {
	if (num < 0) {
		num *= (-1);
	}
	return num;
}
class Point {
	int x;
	int y;
public:
	Point() = default;
	Point(int x, int y) {
		this->x = x;
		this->y = y;
	}
	int getX() {
		return this->x;
	}
	int getY() {
		return this->y;
	}
	bool isXBiggerThan(Point amb) {
		if (this->x > amb.getX()) {
			return true;
		}
		return false;
	}
};
//點集合類,方便修改點數組
class NodeList {
private:
	int num;
public:

	Point pointList[105];
	Point left;
	Point right;
	NodeList() {
		num = 0;
	}
	NodeList(Point left, Point right) {
		num = 0;
		this->left = left;
		this->right = right;
	}
	void add(Point p) {
		pointList[num] = p;
		num += 1;
	}
	bool isEmpty() {
		return num == 0;
	}
	int getLength() {
		return num;
	}
};

bool isAbove(Point point,Point left,Point right) {
	int isAbove = (point.getY() - left.getY()) * (right.getX() - left.getX()) - (point.getX() - left.getX()) * (right.getY() - left.getY());
	if (isAbove > 0) {
		return true;
	}
	else {
		return false;
	}
}
int calS(Point a, Point b, Point c) {
	//利用行列式
	int toAdd = a.getX() * b.getY() + b.getX() * c.getY() + c.getX() * a.getY();
	int toMinus = a.getY() * b.getX() + b.getY() * c.getX() + c.getY() * a.getX();
	return abs(toAdd - toMinus);
}
//該模塊爲核心,在每次遍歷查找,返回最大三角形的面積,這裏不除2,達到精確要求
int doSearch(NodeList list) {
	//結束點
	if (list.isEmpty()) {
		return 0;
	}
	//計算最大面積,然後迭代,必須遍歷兩邊出結果,讓添加節點和尋找一同進行
	int max = 0;
	int biggestP = 0;
	int biggetS = calS(list.left, list.right, list.pointList[biggestP]);
	for (int i = 1; i < list.getLength(); i++) {

		int s = calS(list.left, list.right, list.pointList[i]);
		if (s > biggetS) {
			biggestP = i;
			biggetS = s;
		}

	}
	//生成子節點集
	NodeList left( list.left,list.pointList[biggestP] );
	NodeList right( list.pointList[biggestP] ,list.right );
	int maxX = list.pointList[biggestP].getX();
	for (int i = 0; i < list.getLength(); i++) {
		if (i == biggestP) {
			continue;
		}
		if (list.pointList[i].getX() < maxX) {
			if (isAbove(list.pointList[i],left.left,left.right)) {
				left.add(list.pointList[i]);
			}
			
		}
		else {
			if (isAbove(list.pointList[i],right.left,right.right)) {
				right.add(list.pointList[i]);
			}
		}
	}
	return doSearch(left) + doSearch(right) + biggetS;
}

void cal() {
	int n;
	cin >> n;
	Point points[105];
	for (int i = 0; i < n; i++) {
		int x, y;
		cin >> x >> y;
		points[i] = Point{ x,y };
	}
	int leftx = 0, rightx = 0;
	//劃分上下包
	//找兩端
	for (int i = 1; i < n; i++) {
		Point point = points[i];
		if (point.isXBiggerThan(points[rightx])) {
			rightx = i;
		}
		if (!point.isXBiggerThan(points[leftx])) {
			leftx = i;
		}
	}
	//生成上下包
	Point left = points[leftx];
	Point right = points[rightx];
	NodeList above( left,right );
	NodeList below( left,right );
	//添加節點
	for (int i = 0; i < n; i++) {
		if (i == leftx || i == rightx) {
			continue;
		}
		Point point = points[i];
		bool isAmbAbove = isAbove(point,left,right);
		if (isAmbAbove) {
			above.add(point);
		}
		else {
			below.add(point);
		}
	}
	//執行分治查找
	double answer = (doSearch(above) + doSearch(below)) / 2.0;
	cout << fixed << setprecision(1) << answer << endl;
}
int main() {
	/*
	多組測試數據。第一行是一個整數T,表明一共有T組測試數據。
每組測試數據的第一行是一個正整數N(0< N < = 105),表明了墨點的數量。
接下來的N行每行包含了兩個整數Xi和Yi(0<=Xi,Yi<=2000),表示每個墨點的座標。
每行的座標間可能包含多個空格。
*/	
	int t;
	cin >> t;
	while (--t != -1) {
		cal();
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章