bzoj 1913 signaling 信号覆盖 (极角排序 组合数)

bzoj 1913 signaling 信号覆盖

Description
这里写图片描述

Input

输入第一行包含一个正整数 n, 表示房子的总数。接下来有 n 行,分别表示 每一个房子的位置。对于 i = 1, 2, .., n, 第i 个房子的坐标用一对整数 xi和yi来表 示,中间用空格隔开。
Output

输出文件包含一个实数,表示平均有多少个房子被信号所覆盖,需保证输出 结果与精确值的绝对误差不超过0.01。
Sample Input

4
0 2
4 4
0 0
2 0
Sample Output

3.500
HINT

3.5, 3.50, 3.500, … 中的任何一个输出均为正确。此外,3.49, 3.51,
3.499999,…等也都是可被接受的输出。
【数据范围】
100%的数据保证,对于 i = 1, 2, .., n, 第 i 个房子的坐标(xi, yi)为整数且
–1,000,000 ≤ xi, yi ≤ 1,000,000. 任何三个房子不在同一条直线上,任何四个房子不
在同一个圆上;
40%的数据,n ≤ 100;
70%的数据,n ≤ 500;
100%的数据,3 ≤ n ≤ 1,500。

题目大意:一个平面上n个点,随机选3个点构成一个圆,问期望有多少个点在这个圆内和圆上。数据保证没有4点共圆、3点共线和重点。

这里写图片描述

思路:
考虑四边形,凸四边形对答案的贡献为2,凹四边形对答案的贡献为1。因为没有四点共圆(没有正方形),所以凸四边形中一定有至少一个角为钝角,那么这个钝角点和与其相邻的两个点形成的圆,如果包含剩下的一个点,那么除去这个钝角点,剩下的三个点形成的圆也一定包含这个钝角点,反之剩下的两个圆有贡献。所以一个凸四边形对答案的贡献为2。而在一个凹四边形中,只有外面的三个点形成的圆可以包含里面的一个点。所以一个凹四边形对答案的贡献为1。
设凹四边形个数为a,凸四边形个数为b,那么b=C(n, 4) - a。我们怎么计算凹凸四边形呢?考虑枚举凹四边形的中间点x,以中间点为原点,只要找出的另外三个点形成的三角形不包含原点,那么它就是一组凸四边形。于是我们再枚举一个点p[i],设x-p[i]为从x出发最右侧的那条边,易证只要剩下两个点在直线x-p[i]的同一侧,就是凸四边形。所以当固定x与p[i]时,满足条件的点对数就是凸四边形个数。极角排序,枚举极角差刚刚不小于π的两条边,那么这两条边之间的点和其中一条边上的点(不包含中间点)满足条件。
我们欣喜地发现,按照极角的顺序枚举时,x-p[i]和x-p[i+1]两个状态之间绝大多数的点是重复的。那么枚举的时间复杂度是O(n)。神奇!!!
最后的期望即为(a+2*b)/C(n,3)。

辣鸡的我一开始还天真的用叉乘排极角,但原点不一定在左下角,所以排序时会产生矛盾。膜了膜大神,才发现atan2这个神奇的返回方位角的函数。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#define LL long long 
using namespace std;

LL A, B, n;

struct Node{
    LL x, y;
    double angel;
}a[1510], p[1510];

LL operator*(Node a, Node b){
    return a.x*b.y - a.y*b.x;
}

Node operator-(Node a, Node b){
    return (Node){a.x-b.x, a.y-b.y};
}

double get_angel(Node a){
    return atan2(a.y, a.x);//atan2函数按方位角排序 
}

bool operator<(Node a, Node b){
    return a.angel < b.angel;
}

/*bool operator<(Node aa, Node bb){
    return ((aa - p[0]) * (bb - p[0])) > 0.0;
}*/WA

LL C(int m, int n){//组合数 
    LL rt = 1;
    for(int i=1; i<=n; i++)
        rt *= m-i+1;
    for(int i=1; i<=n; i++)
        rt /= i;
    return rt;
}

LL solve(int x){
    LL tot = C(n-1, 3);//除去枚举点后有n-1个点,可以找出C(n-1, 3)个三角形/四边形 
    int top = 0;
    for(int i=1; i<=n; i++){
        if(i != x) p[++top] = a[i];
        else p[0] = a[i];//原点 
    }
    for(int i=1; i<=top; i++) p[i].angel = get_angel(p[i] - p[0]);
    sort(p+1,p+top+1);
    int R=2, t=0;
    for(int i=1; i<=top; i++){
        while((p[i]-p[0]) * (p[R]-p[0]) >= 0){//固定了x-p[i]保证了所有的p[R]都在一侧
        //所以这t个p[R]中任选两个和p[i]组成的三角形不包含x,就是凸四边形(无三点共线) 
            R = R % top + 1; t++;
            if(R == i) break;//因为要枚举一遍i,所以这样避免重复 
        }
        tot -= C(t, 2);//有 C(t, 2)个 凸四边形 
        t--;//排好序后移动一次有一个点跑到 x-p[i]上去除(无三点共线),其他t-1个点一定满足 
    }
    return tot;
}

int main(){
    scanf("%lld", &n);
    if(n == 3){ puts("3.00"); return 0; }
    for(int i=1; i<=n; i++)
        scanf("%lld%lld", &a[i].x, &a[i].y);
    for(int i=1; i<=n; i++) 
        A += solve(i);
    B = C(n, 4) - A;
    double ans = 2*B+A;//总贡献 
    ans /= C(n, 3);//C(n, 3)个圆 
    printf("%0.6lf", ans+3.0);//每个圆原本就有三个点包含 
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值