UVA - 1634 The Picnic (dp+几何)

问题

m个点,选出若干个,形成凸多边形,使得内部没有点(边界上可以有)。输入点的坐标各不相同,且至少有三个点不共线

分析

这题看上去没什么思路,但有一些问题,看了大佬的代码,说一下思路。
两个问题,如何确保是一个凸多边形? 如何确保无后效性?
保证是凸多边形,使用计算叉乘来保证,保证i,j,k是凸多边形顺时针的三个点,那么从k点看,向量kj在向量ki的顺时针方向
保证无后效性: 先选定最下方(左下方)的一个点center,遍历i,j,center和i,j是凸多边形上的顺时针相邻三个点,他们构成了一个三角形,然后选择一个点k,使得i,j,k构成一个凸多边形的内角(小于等于180度 ),然后就可以将j,k构成的凸多边形面积和三角形面积相加,得到一个新的凸多边形。

使用二维数组,dp[i][j]是(center,i)为一条边,和j或者序号比j小的点构成的最大的合格的凸多边形面积。
假设点的序号 k 1 < k 2 k_1<k_2 ,且 d p [ j ] [ k 1 ] > d p [ j ] [ k 2 ] > 0 dp[j][k_1]>dp[j][k_2]>0 ,那么所有用点 k 2 k_2 的时刻都可以使用点 k 1 k_1 替代,因为角 i j k 1 < i j k 2 ijk_1<ijk_2 ,而且 i j k 1 ijk_1 也可以构成合格的凸多边形,面积更大,所以是更优的选择
cmpare函数
使得点按照**逆时针(别看反了)**排列,a,b,center三点共线按照距离从小到大排列

#include<iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
using namespace std;
const int maxn=105,INF=0x3f3f3f3f;
int T,m,d,ans,dp[maxn][maxn];
struct Point{
    int x,y;
    Point(int x=0,int y=0):x(x),y(y){}
    Point operator - (const Point &rhs) const {
        return Point(x-rhs.x,y-rhs.y);
    }
    bool operator < (const Point &rhs) const;
}points[maxn],center,up[maxn];  //center是每次选定的一个,它构成凸多边形的最下方的顶点(之一),uprig是center上方顶点

int distant(const Point &a){
    return a.x*a.x+a.y*a.y;
}
//计算向量叉乘,叉乘是向量构成的平行四边形面积,乘以0.5就是三角形面积
//叉乘的值>0,b在a的逆时针方向,<0,是顺时针,=0三点共线
int crossProduct(const Point &a,const Point &b){
    return a.x*b.y-b.x*a.y;
}

bool Point::operator < (const Point &rhs) const {
    int t=crossProduct(*this-center,rhs-center);
    if(t!=0) return t>0;  //center的上方顶点按照逆时针顺序排列
    return distant(*this-center)<distant(rhs-center);  //i,j,center三点共线,按照距离排序,和center近的在前面
}

void solve(){
    //dp[i][j]是(center,i)为一条边,和j或者序号比j小的点构成的最大的凸多边形面积
    memset(dp,0,sizeof(dp));
    //将上方的点按照逆时针顺序排列,就是(points[k]-center)向量和x轴正方向的角度小的在前面,角度相同的距离近的在前面
    sort(up+1,up+1+d);
    //凸多边形中,最低点顺时针旋转后的第一个顶点
    for(int i=2;i<=d;++i){
        int j=i-1; //j是i顺时针旋转后的第一个顶点(i,j,center三点不能共线,共线就不是一个三角形了),i,j,center构成一个三角形
        while(j && (!crossProduct(up[i]-center,up[j]-center))) j--;  //如果i,j,center三点共线,寻找下一个合格的点
        bool merge=(j==i-1);  //如果i,j,center共线,也就是merger=false,那么就不能继续叠加三角形,否则会使j在内部
        while(j){
            int k=j-1;
            //i,j,k构成一个角,只有向量kj在ki的顺时针方向或者共线,才可能构成凸多边形,否则或有一个大于180度的角
            while(k && crossProduct(up[i]-up[k],up[j]-up[k])>0) k--;
            int area=abs(crossProduct(up[i]-center,up[j]-center));  //i,j,center之间构成的三角形面积
            //如果i,j,k是一个凸多边形的顶角,那么就可以继续向三角形上添加j,k构成的凸多边形,这一步很重要,k的约束使得凸多边形内部没有点
            //通过j,k的迭代使得三角形center,i,j中没有点,同时center,i,k中也没有点
            if(k) area+=dp[j][k];
            if(merge) dp[i][j]=area;  //如果不共线,就可以计入数组,方便下次叠加
            ans=max(ans,area);  //更新最优解
            j=k; //下一个j点
        }
        if(merge){
            //三角形center,i,j中如果有点,在上面过程中就会被跳过,dp[i][j]=0
            for(int j=2;j<i;++j) dp[i][j]=max(dp[i][j],dp[i][j-1]);
        }
    }
}

int main(void){
    scanf("%d",&T);
    while(T--){
        scanf("%d",&m);
        for(int i=0;i<m;++i){
            scanf("%d%d",&points[i].x,&points[i].y);
        }
        ans=0;
        for(int i=0;i<m;++i){
            //遍历所有的点,首先确定一个最低点i,如果有多个最低点,那么这个点i就是最低点中的最左变得顶点
            //这样就所有的点都在点i的上方
            center=points[i];
            d=0;  //记录可用点的数量
            for(int j=0;j<m;++j){
                if(points[j].y>points[i].y || (points[j].y==points[i].y && points[j].x>points[i].x))
                    up[++d]=points[j];
            }
            solve();
        }
        printf("%.1lf\n",0.5*ans);
    }
    return 0;
}
发布了50 篇原创文章 · 获赞 0 · 访问量 698

猜你喜欢

转载自blog.csdn.net/zpf1998/article/details/104128473