HDU 4353 Finding Mine

——最近学长们开始各种出题了,表示压力很大= =

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4353 

     先简单说说这道题的题意吧,题目讲的大概是个挖金矿的故事,首先分别给出n个普通点(这些点是你可以自己选择的用来圈金矿的点),以及m个金矿所在点。你所需要计算的,就是从n中任意选择x个点(3<=x<=n)组成一个x边形,然后用这个x边形除以x边形内所圈住的金矿的个数,我们暂且将这个比值称之为“性价比”。

好了,题目的要求是输出最高的性价比,那么怎么得到这个性价比呢?首先3000MS的时间虽然足够你暴力的了,但并不意味着真的足够随便暴力枚举,尤其是n的最大取值是200。200个点所能组成的多边形数目是很可怕的。那么,就需要我们仔细分析下题意了....

我们分析下对于一个边数大于三的多边形,它的“性价比”是如何得到的呢?

(每个三角形的面积和)/(每个三角形围住的点的个数和)==(每个三角形的“性价比”之和)/三角形总个数。

也就是说,多边形的性价比来自于每个三角形的性价比的平均值,既然是平均值,那么在所有的三角形中,自然会有比大于平均值、等于平均值和小于平均值的”性价比“存在,那么回头再看看题目要的输出是什么?没错,就是最大的”性价比“,所以最终的结果必然是某个三角形的”性价比“。

也许有人看到这儿会问样例2的问题了。不得不说,值得一提的是这道题的Hint,真的是很误导你不往正确的思路上想。Hint中所取的看似是内个凹四边形“性价比”最高,但其实,它只是恰好等于由(0,0),(0,5),(2,2)或者(0,0),(5,0),(2,2)所组成的三角形的“性价比罢了,而Hint中确有偏偏只给你(0,0),(0,5),(5,0)组成的“性价比”三角形。用心险恶啊~~~

知道了以上内容,这道题唯一的问题就只剩下求每个三角形内金矿个数这一问题了。因为3000MS,所以对于三层for循环O(n^3)遍历所有三角形的个数时间是足够我们“计算几何瞎暴力”的了,但是如果再添一层for循环去遍历m个金矿点在不在该三角形内,时间复杂度就成了可怕的O(n^3*m),超时是必然的了。于是便需要采取一些手段,我的办法是利用叉积来计算对于任意一条有向边AB,计算出它所在直线的右侧的金矿点的个数。并记录在二维数组对应位置record[A][B]中,时间复杂度同样是三次幂的O(n^2*m),但在查询的时候是O(1)的复杂度,查询的方法也很容易理解。

例如:

三角形内部的点的个数=向量AB右侧的点-向量BC右侧的点-向量CA右侧的点。

由此,这道题以总时间复杂度O(n^2*m+n^3)而结束。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#define MIN(a,b) ((a<b)?(a):(b))
const double inf = 1e9;
const double eps = 1e-6;
using namespace std;
int n,m;
int num[210][510];
struct point{
    double x,y;
    bool operator < (const point &a) const{
        return x<a.x;
    }
}pointn[210],pointm[510];
double multi(point p0,point p1,point p2){
    return fabs((double)((p1.x - p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y))*0.5);
}//计算面积,其实只用一个叉积就够了,但是三角形习惯用这个了
int det(point A,point B,point C){
    return (B.x-A.x)*(C.y-A.y)-(B.y-A.y)*(C.x-A.x);
}
void In(point a,point b,int &cnt)
{
    int x1=a.x;
    int x2=b.x;
    for(int i=0;i<m;i++)
        if(x1<=pointm[i].x&&pointm[i].x<x2)
            if(det(a,b,pointm[i])>0)
               cnt++;
}
int main(){
    int t;
    scanf("%d",&t);
    for(int count=1;count<=t;count++){
        double ans=-1;
        scanf("%d%d",&n,&m);
        for(int i=0;i<n;i++)
            scanf("%lf%lf",&pointn[i].x,&pointn[i].y);
        sort(pointn,pointn+n);
        for(int i=0;i<m;i++)
            scanf("%lf%lf",&pointm[i].x,&pointm[i].y);
        for(int i=0;i<n;i++)
            for(int j=i+1;j<n;j++)
                 In(pointn[i],pointn[j],num[i][j]=0);
        for(int i=0;i<n;i++)
        {
            for(int j=i+1;j<n;j++)
            {
                for(int k=j+1;k<n;k++)
                {
                      int cnt=num[i][k]-num[i][j]-num[j][k];
                      if(cnt==0) continue;
                      double area=multi(pointn[i],pointn[j],pointn[k]);
                      double tmp=fabs(area/cnt);
                      if(ans==-1||tmp<ans) ans=tmp;
                }
            }
        }
        if(ans==-1)
        printf("Case #%d: -1\n",count);
        else
        printf("Case #%d: %lf\n",count,ans);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值