应用一
问题描述:平面上有N个点,它们有各自的移动向量,找出在哪一时刻,最远距离的点对距离最短。
输入描述:所有数据都是浮点型,先输入N(2 <= N <= 1000),再输入N个点,初始位置和移动速度向量。
2
0 0 1 0
2 0 -1 0
4
27 27 0 2
58 88 -8 -1
-22 7 1 -1
-38 -26 5 9
输出描述:输出对应时刻和最短距离,保留小数后两位。
1.00 0.00
8.89 81.00
[分析]在N个点中,找出最远点对,引出凸包算法,但是如何描述每个时刻的状态呢(如果是用枚举算法,何时结束计算呢),很难想,因此只能通过三分查找法确定这个时间。所以尝试下凸包+三分法试试。
/*旋转卡壳,求最长点对*/
#include <cstdio>
#include <vector>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn = 300 + 5;
const double eps = 0.000001;
typedef double type_xy;
struct Point {
type_xy x, y, vx, vy, sx, sy;
Point() {}
Point(type_xy x, type_xy y) : x(x), y(y) {}
Point operator + (Point p){ return Point(x + p.x, y + p.y); }
Point operator - (Point p){ return Point(x - p.x, y - p.y); }
Point operator * (type_xy d){ return Point(x*d, y*d); }
bool operator < (const Point& a) const {
if (fabs(x - a.x) > eps) return x < a.x;//!=
else return y < a.y;
}
type_xy dot(Point p) { return x*p.x + y*p.y; }
type_xy det(Point p) { return x*p.y - y*p.x; }
void translate(double time_) {
x = time_ * vx + sx;
y = time_ * vy + sy;
}
};
int N;
Point ps[maxn];
//字典序比较
bool cmp_x(const Point& p, const Point& q) {
if (fabs(p.x - q.x) > eps)
return p.x < q.x;
return p.y < q.y;
}
//求凸包
vector<Point> convex_hull(Point* ps, int n) {
sort(ps, ps + n, cmp_x);
int k = 0; //凸包的顶点数
vector<Point> qs(n * 2); //构造中的凸包
//构造凸包的下侧
for (int i = 0; i < n; i++){
while (k > 1 && (qs[k - 1] - qs[k - 2]).det(ps[i] - qs[k - 1]) <= 0)
k--;
qs[k++] = ps[i];
}
//构造凸包的上侧
for (int i = n - 2, t = k; i >= 0; i--){
while (k > t && (qs[k - 1] - qs[k - 2]).det(ps[i] - qs[k - 1]) <= 0)
k--;
qs[k++] = ps[i];
}
qs.resize(k - 1);
return qs;
}
//距离的平方
double dist(Point p, Point q) {
return (p - q).dot(p - q);
}
double solve() {
vector<Point> qs = convex_hull(ps, N);
int n = qs.size();
if (n == 2){ //特别处理凸包退化的情况
//printf("%.0f\n", );
return dist(qs[0], qs[1]);
}
int i = 0, j = 0; //某个方向上的对踵点对
//求出x轴方向上的对踵点对
for (int k = 0; k < n; ++k){
if (!cmp_x(qs[i], qs[k]))
i = k;
if (cmp_x(qs[j], qs[k]))
j = k;
}
double res = 0;
int si = i, sj = j;
while (i != sj || j != si){ //将方向逐步旋转180度
res = max(res, dist(qs[i], qs[j]));
//判断先转到边i-(i+1)的法线方向还是边j-(j+1)的法线方向
if ((qs[(i + 1) % n] - qs[i]).det(qs[(j + 1) % n] - qs[j]) < 0)
i = (i + 1) % n; //先转到边i-(i+1)的法线方向
else
j = (j + 1) % n; //先转到边j-(j+1)的法线方向
}
//printf("%.0f\n", res);
return res;
}
double get_max_dis(double time_) {
for (int i = 0; i < N; ++i) {
ps[i].translate(time_);
}
return solve();
}
int main() {
//freopen("data/longestpoint.txt","r",stdin);
while (scanf("%d", &N) != EOF){
for (int i = 0; i < N; i++){
scanf("%lf%lf%lf%lf", &ps[i].x, &ps[i].y, &ps[i].vx, &ps[i].vy);
ps[i].sx = ps[i].x;
ps[i].sy = ps[i].y;
}
double l = 0, r = 1000000;
double res_dis = solve();
double res_time = 0;
while (l < r - eps) {
double mid = (l + r) * 0.5;
double mid_mid = (l + mid) * 0.5;
double res1 = get_max_dis(mid);
double res2 = get_max_dis(mid_mid);
if (res1 < res2) {
l = mid_mid;
if (res1 < res_dis) {
res_dis = res1;
res_time = mid;
}
} else {
r = mid;
if (res2 < res_dis) {
res_dis = res2;
res_time = mid_mid;
}
}
}
printf("%.2lf %.2lf\n", res_time, sqrt(res_dis));
}
return 0;
}
应用二
问题描述:一个圆上有N个点,按照角度从小到大输入这些点(假设以y轴正方向为起始方向,顺时针旋转),求最大的弧度点对所对应的角度。
输入描述:先输入n表示有n个点(2<= n <=1e8),接下来n行,每一行是一个精确度为8位的浮点数,表示角度。
4
10.00000000
180.00000000
183.000000
198.000000
输出描述:输出一个浮点数,即最大弧所对应的角度。
173.000000
[分析]:同样暴力,直接枚举每个点对,还是会超时。能否假设圆的半径为1,这样可以把角度转化为(x, y)坐标点,同时问题变为求最远点对(最大弦长))所对应的角度。这样做,主要是因为旋转卡壳求最远点对的时间复杂度由排序决定:O(nlogn),而在求最远点对时,都是线性时间:O(n)
#include <cstdio>
#include <vector>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn = 1e8 + 5;
const double eps = 0.000000000000001;
#define PI 3.14159265358979323846//3.14159265358979
typedef double type_xy;
struct Point
{
type_xy x, y, angle;
Point() {}
Point(type_xy x, type_xy y) : x(x), y(y) {}
Point operator + (Point p){ return Point(x + p.x, y + p.y); }
Point operator - (Point p){ return Point(x - p.x, y - p.y); }
Point operator * (type_xy d){ return Point(x*d, y*d); }
bool operator < (const Point& a) const
{
if (fabs(x - a.x) > eps) return x < a.x;//!=
else return y < a.y;
}
type_xy dot(Point p) { return x*p.x + y*p.y; }
type_xy det(Point p) { return x*p.y - y*p.x; }
};
int N;
Point ps[maxn];
//字典序比较
bool cmp_x(const Point& p, const Point& q) {
if (fabs(p.x - q.x) > eps)
return p.x < q.x;
return p.y < q.y;
}
//求凸包
vector<Point> convex_hull(Point* ps, int n) {
sort(ps, ps + n, cmp_x);
int k = 0; //凸包的顶点数
vector<Point> qs(n * 2); //构造中的凸包
//构造凸包的下侧
for (int i = 0; i < n; i++) {
while (k > 1 && (qs[k - 1] - qs[k - 2]).det(ps[i] - qs[k - 1]) <= 0)
k--;
qs[k++] = ps[i];
}
//构造凸包的上侧
for (int i = n - 2, t = k; i >= 0; i--) {
while (k > t && (qs[k - 1] - qs[k - 2]).det(ps[i] - qs[k - 1]) <= 0)
k--;
qs[k++] = ps[i];
}
qs.resize(k - 1);
return qs;
}
//距离的平方
double dist(Point p, Point q) {
return (p - q).dot(p - q);
}
double solve() {
vector<Point> qs = convex_hull(ps, N);
int n = qs.size();
if (n == 2){ //特别处理凸包退化的情况
//printf("%.0f\n", );
return dist(qs[0], qs[1]);
}
int i = 0, j = 0; //某个方向上的对踵点对
//求出x轴方向上的对踵点对
for (int k = 0; k < n; ++k) {
if (!cmp_x(qs[i], qs[k]))
i = k;
if (cmp_x(qs[j], qs[k]))
j = k;
}
double res = 0;
int si = i, sj = j;
while (i != sj || j != si) { //将方向逐步旋转180度
res = max(res, dist(qs[i], qs[j]));
//判断先转到边i-(i+1)的法线方向还是边j-(j+1)的法线方向
if ((qs[(i + 1) % n] - qs[i]).det(qs[(j + 1) % n] - qs[j]) < 0)
i = (i + 1) % n; //先转到边i-(i+1)的法线方向
else
j = (j + 1) % n; //先转到边j-(j+1)的法线方向
}
//printf("%.0f\n", res);
return res;
}
int main() {
//freopen("data/sogou.txt","r",stdin);
double angle;
while (scanf("%d", &N) != EOF) {
for (int i = 0; i < N; i++) {
scanf("%lf", &angle);
ps[i].x = sin(angle * PI / 180.0);//假设半径为1,根据角度求坐标。
ps[i].y = cos(angle * PI / 180.0);
ps[i].angle = angle;
}
double res = sqrt(solve());//求出最远点对距离(即,弦长)
printf("%.8lf\n", asin(res/2.0) * 360.0 / PI);
}
return 0;
}