【计几】旋转卡壳题集

本文介绍了平面几何中的三个算法问题:使用旋转卡壳法求解最远点对、最小矩形覆盖以及凸包中的最大三角形面积。每个问题都涉及到了单调指针的运用,通过排序和单调栈来优化复杂度。对于最远点对,文章给出了如何找到所有对踵点并更新答案的思路;对于最小矩形覆盖,维护了三个单调指针来找到最小矩形;而对于最大三角形面积,通过旋转卡壳法找到最大面积的三角形。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


Acwing 2938. 周游世界【平面最远点】

题意:求平面欧氏距离最远点

题解:AcWing 2938. 周游世界 题解

思路:单调找出所有对踵点,更新答案。

AC代码:

#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;

const int N = 5e4+10;
typedef long long LL;

struct point{
    LL x,y;
    bool operator < (point a){
        if(x != a.x) return x < a.x;
        return y < a.y;
    }
    bool operator == (point a) { return x == a.x && y == a.y ;}
}P[N];
point operator - (point a,point b) { return {a.x - b.x, a.y - b.y};}
LL cross(point a ,point b) { return a.x*b.y - a.y * b.x; }
LL area(point a,point b,point c) { return cross(b - a, c - a); }
LL get_dis(point a,point b) { return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y); }


int sta[N],tp;
void andrew(int n)
{
    sort(P,P+n);
    n=unique(P,P+n)-P;

    for(int i=0;i<n;i++)
    {
        while(tp >=2 && area(P[sta[tp-2]], P[sta[tp-1]], P[i]) <= 0) tp--;// ***
        sta[tp++]=i;
    }
    int v=tp;
    for(int i=n-2; i>=0 ;i--)
    {
        while(tp >v && area(P[sta[tp-2]], P[sta[tp-1]], P[i]) <= 0) tp--;
        sta[tp++]=i;
    }
    tp--;
}
// *** 注意这里要防止出现多点一线的情况。容易证明多点一线的情况,最优解只存在于两端上,因此中间的点忽略即可。
LL rotate_calipers()
{
    if(tp <= 2) return get_dis(P[sta[0]], P[sta[tp-1]]);
    LL res=0;
    for(int i=0, j=2; i<tp; i++)
    {
        auto & d = P[sta[i]], & e = P[sta[i + 1]];
        while(area(d, e, P[sta[j]]) < area(d , e, P[sta[j + 1]])) j= (j + 1) % tp;
        res=max(res, max(get_dis(d, P[sta[j]]),get_dis(e, P[sta[j]])));
    }
    return res;
}


int main()
{
    int n; scanf("%d",&n);
    for(int i=0;i<n;i++) scanf("%lld %lld",&P[i].x,&P[i].y);

    andrew(n);

    printf("%lld",rotate_calipers());

    system("pause");
    return 0;
}


Acwing 2142. 最小矩形覆盖【多单调指针】

题意:求最小矩形覆盖

思路:维护三个单调指针

AC代码:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
using namespace std;

const int N = 5e4+10;
typedef long long LL;
const double eps = 1e-10;
const double pi = acos(-1); 

int sign(double x){
    if(fabs(x) < eps) return 0;
    if(x < 0) return -1;
    return 1;
}
int dcmp(double x, double y){
    if(fabs(x - y) < eps) return 0;
    if(x < y) return -1;
    return 1;
}

struct point{
    double x,y;
    bool operator < (point a){
        if(dcmp(x, a.x)!=0) return dcmp(x, a.x)==1 ? 0 : 1;
        return y < a.y;
    }
    bool operator == (point a) { return dcmp(x, a.x) == 0 && dcmp(y, a.y) == 0;}
}P[N];
point operator - (point a,point b) { return {a.x - b.x, a.y - b.y};}
point operator + (point a,point b) { return {a.x + b.x, a.y + b.y};}
point operator * (point a,double b) { return {a.x * b, a.y * b}; }
point operator / (point a,double b) { return {a.x / b, a.y / b}; }

double cross(point a ,point b) { return a.x*b.y - a.y * b.x; }
double dot(point a,point b) { return a.x * b.x + a.y * b.y; }
double area(point a,point b,point c) { return cross(b - a, c - a) / 2; }
double get_dis(point a,point b) { return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y); }
double length(point a) { return sqrt(a.x * a.x + a.y * a.y); }
double project(point a,point b, point c) { return dot(c - a, b - a)/ length(b - a); }
point unit(point a) { return a / length(a); }
point rotate(point a,double rad) { return {a.x * cos(rad) - a.y * sin(rad), a.x * sin(rad) + a.y * cos(rad)}; }


int sta[N],tp;
void andrew(int n)
{
    sort(P,P+n);
    n = unique(P,P+n) - P;
    for(int i = 0; i < n; i++)
    {
        while(tp > 1 &&  sign(area(P[sta[tp-2]],P[sta[tp-1]],P[i])) != 1) tp--;// ***
        sta[tp++]=i;
    }
    int v=tp;
    for(int i = n-2 ;i>=0 ; i--)
    {
        while(tp > v && sign(area(P[sta[tp-2]], P[sta[tp-1]], P[i])) != 1) tp--;
        sta[tp++]=i;
    }
    tp--;
}
// *** 注意这里要防止出现多点一线的情况。容易证明多点一线的情况,最优解只存在于两端上,因此中间的点忽略即可。

double ans=1e18;
point ans_point[4];

void rotate_calipers()
{
    for(int i = 0, j =2, k = 2, l = 2; i < tp; i++)
    {
        while(area(P[sta[i]], P[sta[i + 1]], P[sta[j]]) < area(P[sta[i]], P[sta[i + 1]], P[sta[j + 1]])) j = (j + 1) % tp; // ***
        while(project(P[sta[i]], P[sta[i + 1]], P[sta[k]]) < project(P[sta[i]], P[sta[i + 1]], P[sta[k + 1]])) k = (k + 1) % tp;
        if(i==0) l = j;
        while(project(P[sta[i]], P[sta[i + 1]], P[sta[l]]) > project(P[sta[i]], P[sta[i + 1]], P[sta[l + 1]])) l = (l + 1) % tp;


        double w = project(P[sta[i]], P[sta[i + 1]], P[sta[k]]) - project(P[sta[i]],P[sta[i + 1]], P[sta[l]]);
        double h = 2 * area(P[sta[i]], P[sta[i + 1]], P[sta[j]]) / length(P[sta[i + 1]] - P[sta[i]]);

        // cout<<"# "<<i<<" "<<w<<" "<<h<<endl;
        // cout<<"## "<<i<<" "<<j<<" "<<k<<" "<<l<<endl;
        if(w * h < ans)
        {
            ans = w * h;
            ans_point[0] = P[sta[i]] + unit(P[sta[i + 1]] - P[sta[i]]) * project(P[sta[i]], P[sta[i + 1]], P[sta[k]]);
            ans_point[3] = P[sta[i]] + unit(P[sta[i + 1]] - P[sta[i]]) * project(P[sta[i]], P[sta[i + 1]], P[sta[l]]);
            ans_point[1] = ans_point[0] + unit(rotate(P[sta[i + 1]]- P[sta[i]], pi / 2)) * h;
            ans_point[2] = ans_point[3] + ans_point[1] - ans_point[0];
            
            // cout<<i<<" "<<j<<" "<<k<<" "<<l<<endl;
            // cout<<"# "<<w<<" "<< h <<endl;
            // cout<<(P[sta[k]] - P[sta[l]]).x<<" "<<(P[sta[k]] - P[sta[l]]).y<<endl;
        }
    }
}
// *** 注意:这里不能用eps判断大小,直接单调性判断即可。因为fabs(ΔS)可能等于0小于eps,就会跳出循环,出现错误。而凸包本身就有单调性,就可以不用eps来判断。

int main()
{
    int n; scanf("%d",&n);
    for(int i=0;i<n;i++) scanf("%lf %lf",&P[i].x,&P[i].y);

    andrew(n);

    // for(int i=0; i<=tp; i++) cout<<P[sta[i]].x<<" "<<P[sta[i]].y<<endl;

    rotate_calipers();

    printf("%.5f\n",ans);
    int k=0;
    for(int i=0; i < 4; i++)
        if(dcmp(ans_point[k].y,ans_point[i].y) == 1 || dcmp(ans_point[k].y, ans_point[i].y) ==0 && dcmp(ans_point[k].x, ans_point[i].x) == 1) k = i;
    
    for(int i=0; i<4; i++ , k = (k + 1) %4)
    {
        if(fabs(ans_point[k].x) < 5e-6) ans_point[k].x = 0;
        if(fabs(ans_point[k].y) < 5e-6) ans_point[k].y = 0;
        printf("%.5f %.5f\n",ans_point[k].x, ans_point[k].y);
    }

    return 0;
}

HDU 2022(凸包中的最大三角形面积)

题意:求凸包中的最大三角形面积

题解:POJ 2079 Triangle 旋转卡壳

思路:看了题解,还是不知道为什么有单调性。

  • 当前有i,j,k三指针,i向右移后,以i-j为底边,先让k单调跑(j先跑的话肯定跑不动)。找到最优点后,再以i-k为底边,让j单调跑。有个要注意的地方是:如果指针i与单调指针碰撞时,要后移单调指针避免碰撞,因为k的后移依赖于i-j为底边
#include<iostream>
#include<cstdio>
#include<cmath>
#include<map>
#include<algorithm>
using namespace std;

typedef long long LL;
const int N=5e4 + 10;
const double eps = 1e-9;

int dcmp(double x, double y){
    if(fabs(x - y) < eps) return 0;
    if(x < y) return -1;
    return 1;
}
int sign(double x){
    if(fabs(x) < eps) return 0;
    if(x < 0) return -1;
    return 1;
}

struct point{
    double x, y;
    bool operator < (const point a) const {
        if(dcmp(x, a.x)) return dcmp(x, a.x) < 0;
        return dcmp(y, a.y) < 0;
    }
    bool operator == (const point a) const {
        return !dcmp(x, a.x) && !dcmp(y, a.y);
    }
};
point operator + (point a, point b) { return {a.x + b.x, a.y + b.y}; }
point operator - (point a, point b) { return {a.x - b.x, a.y - b.y}; }
point operator * (point a, double b) { return {a.x * b, a.y * b}; }
point operator / (point a, double b) { return {a.x / b, a.y / b}; }

double length(point a) { return sqrt(a.x * a.x + a.y * a.y); }
double cross(point a, point b) { return a.x * b.y - a.y * b.x; }
double dot(point a, point b) { return a.x * b.x + a.y * b.y; }
double area(point a, point b, point c) { return cross(b - a, c - a) / 2; }
double project(point a, point b, point c) { return dot(b - a, c - a) / length(b - a); }


point P[N];
int n, m;
int sta[N];

void andrew()
{
    m = 0;
    sort(P, P + n);
    for(int i=0; i<n; i++)
    {
        while(m > 1 && sign(area(P[sta[m - 2]], P[sta[m - 1]], P[i])) <= 0) m--;
        sta[m++] = i;
    }
    int v = m;
    for(int i=n - 2; i>=0 ; i--)
    {
        while(m > v && sign(area(P[sta[m - 2]], P[sta[m - 1]], P[i])) <= 0) m--;
        sta[m++] = i;
    }
    if(n > 1) m--;
}

double sol()
{
    double res = 0;
    for(int i=0, j = 1, k = 1; i<m; i++)
    {
        if(i == j) j++; //旋转卡壳要注意,有些特殊的题,如果指针i与单调指针碰撞时,要后移单调指针避免碰撞
        				//k的后移依赖于i-j为底边,要防止碰撞
        while(area(P[sta[i]], P[sta[j]], P[sta[k]]) < area(P[sta[i]], P[sta[j]], P[sta[(k + 1) % m]])) k = (k + 1) % m;
        while(area(P[sta[i]], P[sta[j]], P[sta[k]]) < area(P[sta[i]], P[sta[(j + 1) % m]], P[sta[k]])) j = (j + 1) % m;

        // cout<<"# "<<i<<" "<<j<<" "<<k<<endl;
        res = max(res, area(P[sta[i]], P[sta[j]], P[sta[k]]));
    }
    return res;
}

int main()
{
    while(scanf("%d", &n) != EOF)
    {
        for(int i=0 ;i<n ;i++) scanf("%lf %lf", &P[i].x, &P[i].y);
        andrew();
        printf("%.2f\n", sol());
    }

    system("pause");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值