好蠢哦,很多细节错误,搞得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函数,并将创建对象修改为{}初始化即可
另一种利用指针的解法在我另一篇文章中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();
}
}