bzoj1132 [POI2008]Tro(叉积)

83 篇文章 0 订阅
53 篇文章 0 订阅

bzoj1132 [POI2008]Tro

原题地址http://www.lydsy.com/JudgeOnline/problem.php?id=1132

题意:
平面上有N个点. 求出所有以这N个点为顶点的三角形的面积和 N<=3000

数据范围
N在[3,3000] ,N个点的坐标值在[0,10000]
保留一位小数,误差不超过0.1

题解:
好题。
我想了两种做法,
第一种:考虑一般求多边形面积的方法。
这里写图片描述
S=S蓝1+S蓝2-S红
其实就是计算每条边的贡献。
对于x以右的点极角排序,
ab 这样的边贡献+1
(保存一个两两边贡献的后缀和)
x与a这样的点连边的贡献就(前面的点的个数-后面的点的个数)

正解:
考虑叉积到底是个什么。
x1y2-x2y1
如果a对于x的坐标(x1,y1),b对于x的坐标(x2,y2)
提个公因数,
x1贡献 ∑y2
y1贡献-∑x2

因为要用叉积算,要保证>0,于是让它有序就行。
先按x排序,枚举每个点,他之右的极角排序,然后存个前缀和算。


一个困扰我很久的点:
第一遍排序:bool cmp1(const Poi &A,const Poi &B){return A.x<B.x;}
结果:WA
第一遍排序:bool cmp1(const Poi &A,const Poi &B){return A.x<B.x||(A.x==B.x&&A.y<B.y);}
结果:AC

一组数据:
(10,1)(10,2)(10,3) ,(15,2)
正确答案是10.0
但对于第一种排序,显然它对于前三个点的顺序不作要求,
当顺序为:(10,2)(10,3) (10,1),(15,2)时,
答案为 5.0,错了!!!
而对称的(10,2)(10,1) (10,3),(15,2)
答案则是正确的 10.0
为什么?感谢Doggu非常耐心的答疑。
打印出过程发现,同样第一个数取(10,2),那么剩下三个点对于他的坐标分别为:
(5,0)(0,1)(0,-1)
第一种的极角排序:(0,-1)(0,1)(5,0)
正常的极角排序:(0,-1)(5,0)(0,1)
极角排序不对!!!
考虑到我们为了追求常数小,比较极角用的不是atan2,而是叉积:

bool cmp2(const Poi &A,const Poi &B){return A.x*B.y-B.x*A.y>0;}

那么对于(0,-1)和(0,1)他们的叉积为0,其实是互相小于的,一旦比较顺序不对,排序就不对。

因此必须按x为第一关键字,y为第二关键字排,才不会出现这种上下都有的情况。

下次用叉积也要小心了。


代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
using namespace std;
const int N=3005;
struct Poi
{
    int x,y;
    Poi(){}
    Poi(int x,int y):x(x),y(y){}
}P[N],v[N];
Poi operator-(const Poi &A,const Poi &B){return Poi(A.x-B.x,A.y-B.y);}
bool cmp1(const Poi &A,const Poi &B){return A.x<B.x||(A.x==B.x&&A.y<B.y);}
bool cmp2(const Poi &A,const Poi &B){return A.x*B.y-B.x*A.y>0;}
int n;
LL ret=0;
void solve()
{
    sort(P+1,P+n+1,cmp1);
    for(int i=1;i<n-1;i++)
    {
        int cnt=0; LL sx=0,sy=0;
        for(int j=i+1;j<=n;j++)
        {
            v[++cnt]=P[j]-P[i];
        }
        sort(v+1,v+cnt+1,cmp2);
        for(int j=1;j<=cnt;j++)
        {       
            ret-=1LL*v[j].x*sy;
            ret+=1LL*v[j].y*sx;
            sx+=v[j].x; sy+=v[j].y;
        }
    }
    printf("%lld.%lld\n",ret>>1,5LL*(ret&1));
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d%d",&P[i].x,&P[i].y);
    solve();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值