poj1259 The picnic【最大空凸包详解 计算几何+动态规划】

本文介绍了一种寻找平面上给定点集中最大空凸包的算法。通过枚举每个点作为凸包底部点,并利用极角排序及动态规划方法确定最大凸包面积。文章详细解释了两种算法实现方案及其优化过程。

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

题目大意:

给定平面上 n n 个点,求面积最大的空凸包。1n100

解题思路:

感谢http://blog.youkuaiyun.com/nyroro/article/details/45268767这篇博客的讲解。

算法一

先枚举每个点作为凸包的最下面的点O,不考虑它下方的点,其余点以O为原点极角排序。

再枚举凸包最后一个点 i i 与O相连,设dp[i][j](i>j)表示组成凸包的最后一个三角形是 Oij O i j 的最大凸包面积,那么容易得到dp方程:

dp[i][j]=max(SΔOij+dp[j][k]),(ΔOijOikij) d p [ i ] [ j ] = m a x ( S Δ O i j + d p [ j ] [ k ] ) , ( Δ O i j 内 无 点 且 边 O i 上 无 点 且 k 在 i j → 右 侧 保 证 凸 性 )

注意到如果边 Oi O i 上有点,它就只能作为凸包的边界,所以不能更新 dp[i][j] d p [ i ] [ j ] ,但还是要用 max(SΔOij+dp[j][k]) m a x ( S Δ O i j + d p [ j ] [ k ] ) 更新最终答案,否则用 dp[i][j] d p [ i ] [ j ] 更新答案。

但这样单次dp的复杂度是 O(n3) O ( n 3 ) 的,考虑优化这个过程,所以有了算法二。

算法二

还是枚举凸包最后一个点 i i ,dp意义也相同。可以发现,合法的j可用以下方法得到:

1.令 j1=i1 j 1 = i − 1 ,可以保证 ΔOij Δ O i j 合法;如果 j1 j 1 Oi O i 上,那么从大到小枚举第一个不在 Oi O i 上的点作为 j1 j 1 ,但这样 Oi O i 上就有点了,处理方法同上。

2.令 j2 j 2 为最大的在 ij1 i j 1 → 右侧的点, j3 j 3 为最大的在 ij2 i j 2 → 右侧的点……可以证明这样可以找出所有合法的 j j

3.注意到找出的j序列是满足凸性的,且 dp[i][jn] d p [ i ] [ j n ] 的第一个可以转移来的点就是 jn+1 j n + 1 ,所以可以用 g[i][j] g [ i ] [ j ] 表示 max(dp[i][k],1kj) m a x ( d p [ i ] [ k ] , 1 ≤ k ≤ j ) ,那么就有 dp[i][jn]=SΔOij+g[i][jn+1] d p [ i ] [ j n ] = S Δ O i j + g [ i ] [ j n + 1 ]
注:1~j中不合法的点dp值为0,不影响 g g 的更新,所以g数组一定满足凸性;但如果 Oi O i 上有点还是不更新 dp d p g g ,但还是要更新最终答案。

这样枚举原点是O(n)的,枚举凸包最后一点 i i O(n)的,枚举合法的 j j O(n)的,转移是 O(1) O ( 1 ) 的,更新 g g 数组是O(n)的,所以总复杂度是 O(n3) O ( n 3 ) 的。

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

int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();(c!='-')&&(c<'0'||c>'9');c=getchar());
    if(c=='-')f=-1,c=getchar();
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}

const int N=105;
struct point
{
    double x,y;
    point(){}
    point(double _x,double _y):x(_x),y(_y){}
    inline friend point operator - (const point &a,const point &b)
    {return point(a.x-b.x,a.y-b.y);}
    inline friend double operator * (const point &a,const point &b)
    {return a.x*b.y-a.y*b.x;}
    inline double dis(){return x*x+y*y;}
}a[N],p[N],O;
int T,n,m;
double dp[N][N],ans;

inline bool cmp(const point &a,const point &b)
{
    double res=(a-O)*(b-O);
    if(res)return res>0;
    return (a-O).dis()<(b-O).dis();
}

void solve()
{
    memset(dp,0,sizeof(dp));
    sort(p+1,p+m+1,cmp);
    for(int i=1;i<=m;i++)
    {
        int j=i-1;
        while(j&&!((p[i]-O)*(p[j]-O)))j--;
        bool bz=(j==i-1);
        while(j)
        {
            int k=j-1;
            while(k&&(p[i]-p[k])*(p[j]-p[k])>0)k--;
            double area=fabs((p[i]-O)*(p[j]-O))/2;
            if(k)area+=dp[j][k];
            if(bz)dp[i][j]=area;
            ans=max(ans,area),j=k;
        }
        if(bz)for(int j=1;j<i;j++)dp[i][j]=max(dp[i][j],dp[i][j-1]);
    }
}

int main()
{
    //freopen("lx.in","r",stdin);
    T=getint();
    while(T--)
    {
        n=getint();ans=0;
        for(int i=1;i<=n;i++)a[i].x=getint(),a[i].y=getint();
        for(int i=1;i<=n;i++)
        {
            O=a[i],m=0;
            for(int j=1;j<=n;j++)
                if(a[j].y>a[i].y||a[j].y==a[i].y&&a[j].x>a[i].x)p[++m]=a[j];
            solve();
        }
        printf("%0.1lf\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值