【2011-2012 ACM-ICPC Pacific Northwest Regional Contest C】A Classic Myth【点集最小平行四变形覆盖】

题意:

       给出n个点的坐标,求一个面积最小的平行四边形覆盖这n个点,输出平行四边形面积。

 

思路:

       类似于求点集的最小矩形覆盖。求矩形时,我们可以得知凸包上至少有一条边与矩形边重合。而对于平行四边形来说,凸包上至少有两条边与最后的平行四边形重合。

       由于n只有1000,因此我们可以n^2枚举,求出所有两条边构成的平行四边形。而对于平行四边形面积,我们可以知道,只要求出平行四边形两条边到对面平行边的距离,再知道两条边之间的夹角,我们就可以求出这个平行四边形。

       因此我们只需要预处理出对于每条边来说,凸包上点距离该边的最远距离即可。

 

代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <cmath>
#include <algorithm>
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x) cout << "x: " << x << endl;
#define LOG2(x,y) cout << "x: " << x << ", y: " << y << endl;
#define LOG3(x,y,z) cout << "x: " << x << ", y: " << y << ", z: " << z << endl;
#define LOG4(x,y,z,h) cout << "x: " << x << ", y: " << y << ", z: " << z << ", h: " << h << endl;
#define pi acos(-1.0)
#define cross(p1,p2,p3) ((p2.x-p1.x)*(p3.y-p1.y)-(p3.x-p1.x)*(p2.y-p1.y)) //向量(p1,p2)与(p1,p3)叉乘
#define crossOp(p1,p2,p3) sign(cross(p1,p2,p3))	//判断正负,顺时针为负, 为0则代表三点共线
using namespace std;

//实数比较
typedef double db;
const db EPS = 1e-9;
inline int sign(db a) {return a < -EPS ? -1 : a > EPS; } //返回-1表示a < 0, 1表示a > 0, 0表示a = 0
inline int cmp(db a, db b) {return sign(a-b); } //返回-1表示a < b, 1表示a > b,0表示 a==b

//点类
struct P {
	db x,y;
	P() {}
	P(db _x, db _y) : x(_x), y(_y) {}
	P operator+(P p) { return {x+p.x, y+p.y}; }
	P operator-(P p) { return {x-p.x, y-p.y}; }
	P operator*(db d) { return {x*d, y*d}; }
	P operator/(db d) { return {x/d, y/d}; }
	db dot(P p) { return x*p.x+y*p.y; }	//点积
	db det(P p) { return x*p.y-y*p.x; } //叉积
	P rot(db an) { return {x*cos(an)-y*sin(an),x*sin(an)+y*cos(an)}; }	//旋转
	db abs() { return sqrt(abs2()); }
	db abs2() { return x*x+y*y; }
	db disTo(P p) { return (*this-p).abs(); }
	//此时在x负半轴上的点, 排序结果是最小的。如果去掉sign(x)>=0, 则排序结果是最大的
	int quad() const { return sign(y) == 1 || (sign(y) == 0 && sign(x) >= 0); } //判断该点是否在x轴上方或x轴上

	bool operator<(P p) const {
		int c = cmp(x, p.x);
		if (c) return c == -1;	//先判断x大小
		return cmp(y, p.y) == -1;	//再判断y大小
	}
	bool operator==(P p) const {
		return cmp(x, p.x) == 0 && cmp(y, p.y) == 0;
	}
	bool operator!=(P p) const{
		return (cmp(x, p.x) || cmp(y,p.y));
	}
};

db area(vector<P> ps){	//凸包面积
	db ret = 0; rep(i,0,ps.size()-1) ret += ps[i].det(ps[(i+1)%ps.size()]);
	return ret/2;
}

db perimeter(vector<P> ps){	//凸包周长
	db ret = 0; rep(i,0,ps.size()-1) ret += ps[i].disTo(ps[(i+1)%ps.size()]);
	return ret;
}

db dot(P A, P B, P C){	//三点点积
	return (B-A).dot(C-A);
}

//求凸包
vector<P> convexHull(vector<P> ps) {
	int n = ps.size(); if(n <= 1) return ps;
	sort(ps.begin(),ps.end());
	vector<P> qs(n*2); int k = 0;
	for(int i = 0; i < n; qs[k++] = ps[i++])
		while(k > 1 && crossOp(qs[k-2],qs[k-1],ps[i]) <= 0) --k;	//把 <= 改成 <, 即可将凸包边上的点也包括在凸包中, 不稳定凸包问题 
	for(int i = n-2, t = k; i >= 0; qs[k++] = ps[i--])
		while(k > t && crossOp(qs[k-2],qs[k-1],ps[i]) <= 0) --k;
	qs.resize(k-1);
	return qs;
}

//最小矩形覆盖
db minRectangleCover(vector<P> ps){
	//凸包点集顺序按逆时针
	int n = ps.size();	
	if(n < 3) return 0.0;
	ps.push_back(ps[0]);
	db ans = -1;
	vector<db> maxh; maxh.clear();
	int r = 1;
	rep(i,0,n-1){
		db d = ps[i].disTo(ps[i+1]);
		while(sign(cross(ps[i],ps[i+1],ps[r+1])-cross(ps[i],ps[i+1],ps[r])) >= 0) //叉积最大即为到点r到ps[i+1]-ps[i]这条边的距离最大
			r = (r+1)%n;
		maxh.push_back(fabs(cross(ps[i],ps[i+1],ps[r]))/d);
	}
	rep(i,0,n-1){
		int j = (i+1)%n;
		while(j != i){
			db h1 = maxh[i], h2 = maxh[j];
			db sinn = fabs((ps[i+1]-ps[i]).det(ps[(j+1)%n]-ps[j]))/(ps[i].disTo(ps[i+1])*ps[j].disTo(ps[(j+1)%n]));
			if(ans  < 0 || ans > h1*h2/sinn) ans = h1*h2/sinn;
			j = (j+1)%n;
		}
	}
	return ans;
}

int main()
{
	int _,n; scanf("%d",&_);
	vector<P> tp;
	rep(k,1,_)
	{
		tp.clear();
		scanf("%d",&n);
		rep(i,1,n){
			db x,y; scanf("%lf%lf",&x,&y);
			tp.push_back({x,y});
		}
		tp = convexHull(tp);
		printf("Swarm %d Parallelogram Area: %.4f\n",k,minRectangleCover(tp));
	}
	return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gene_INNOCENT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值