天天看點

SWUST OJ 249: 凸包面積(分治法求凸包面積)題目描述思路

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

題目描述

原題連結點我

SWUST OJ 249: 凸包面積(分治法求凸包面積)題目描述思路
總結一下,就是簡單的分治法求凸包面積

思路

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

分治法:将問題劃分為同一類型的若幹個子問題,問題足夠小時求解,最後合并結果

首先,我們将點集劃分為兩個部分,然後逐漸求解

如何劃分?

為了最好的劃分效果,我們希望找到兩個點,能劃分點集,同時是凸包的定點。

我們選擇水準方向上最兩邊的點,作為第一次劃分的依據

SWUST OJ 249: 凸包面積(分治法求凸包面積)題目描述思路

第二步求子集的解

暫時隻考慮上半部分(因為下半部分可以相同方法解決)

我們希望能找到一個點,能劃分子集,同時是頂點,最能肯定的一件事是

距離第一次劃分的邊最遠的點一定是我們需要的點。

SWUST OJ 249: 凸包面積(分治法求凸包面積)題目描述思路

如何計算?

考慮到距離涉及開方、除法等操作,我們可以利用三角形的面積,

如下圖,找到面積最大的點即可,

面積怎麼算?

我們線上性代數中,得到過這個問題的答案,給出三點坐标,得到面積

通過三個點的坐标求出三角形面積的公式 當三個點A、B、C的坐标分别為A(x1,y1)、B(x2,y2)、C(x3、y3)時,三角形面積為, S=(x1y2-x1y3+x2y3-x2y1+x3y1-x2y2)。
SWUST OJ 249: 凸包面積(分治法求凸包面積)題目描述思路

下一步劃分

觀察上圖,我們發現:三角形内部的點我們是不用考慮的,可以假設目前連線就是三角形的邊,你就能得到上面的結論,

于是我們再次縮小

SWUST OJ 249: 凸包面積(分治法求凸包面積)題目描述思路

現在,隻需要仿照前一步就能得到我們要的頂點。

如何解決SWUST OJ 249?

注意到題中需要的是面積,我們稍加觀察就能發現:

上半部分的面積可以通過三部分的三角形面積相加得到!!

這三部分分别是左端點,右端點,最高點以及由他們劃分出來的子三角形構成,

你隻需要将計算得來的面積保留下來,在最後一步合并,就做到了

(另外,上述求三角形面積的公式中,設計除2,這将帶來小數,是以我們可以在最後對所有的結果和相加後再除2,這樣避免了因為精度導緻問題的可能)

SWUST OJ 249: 凸包面積(分治法求凸包面積)題目描述思路

代碼如下:

C++11去掉abs函數,并将建立對象修改為{}初始化即可

另一種利用指針的解法在我另一篇文章中SWUST OJ 249凸包面積 分治法解法二,指針更新版

#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();
	}
}
           

繼續閱讀