——最近学长们开始各种出题了,表示压力很大= =
题目链接: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;
}