好蠢哦,很多細節錯誤,搞得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();
}
}