踢罐子 [几何+乱搞]

题面

大概题意:

平面上有n个点,其中任意2点不重合,任意3点不共线。

我们等概率地选取一个点A,再在剩下的n-1个点中等概率地选取一个点B,再在剩下的n-2个点中等概率地选取一个点C。

然后我们计算伤害倍率d。作ABC外接圆,每一个位于弧BC和线段BC之间的点计1倍,每一个位于弧BC上的点(包括B,C两点)计1/2倍,特别的,点A计1倍。将这些倍率全部加起来得到伤害倍率d。

给定这n个点的坐标,你需要求出d的期望。为了简单起见,你只需要输出dn(n-1)(n-2)2的值,可以看出这是一个整数。

思路

转化

先把任意三个点构成的六种选择方法合并,发现就是选出三角形,求外接圆周和弦之间的点数

然后考虑任意四个点的贡献

发现当四个点构成凸四边形的时候,任意选三个点ABC出来,需要第四个点D,使得$A+D<\pi$才可以,此时有4的贡献

如果等于$\pi$则是四点共圆,同样有4的贡献

然后发现当四个点构成凹四边形的时候,任选三个点,第四个点都没有贡献

所以本题变成了求凸四边形个数

求凸四边形个数的$O(n^2 \log n)$算法

对于每个点,把剩下的所有点按照和它的连线的斜率排序,求斜率可以用atan2l函数(加上l避免爆精度)

然后,考虑两个点的连线,设连线两侧的点数分别是$L$和$R$(注意这里要判断,不能构成了一个箭头的形状)

发现构成凸四边形的两个点在同侧的有$(L-1)L+(R-1)R$种情况,两个点在异侧的有$(L*R)$种情况

列个方程可以知道,总的个数就是这两个东西相减除以二

其余详细见代码

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cassert>
#include<cmath>
#define ll long long
const double pi=acos(-1.0);
using namespace std;
inline int read(){
    int re=0,flag=1;char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-') flag=-1;
        ch=getchar();
    }
    while(isdigit(ch)) re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
    return re*flag;
}
int n,top;ll ans=0;
double x[1010],y[1010],lis[2010];
int main(){
    n=read();int i,j,k,l,r;
    for(i=1;i<=n;i++){
        x[i]=read();y[i]=read();
    }
    ans=4ll*n*(n-1)*(n-2);
    for(i=1;i<=n;i++){
        top=0;
        for(j=1;j<=n;j++){
            if(i==j) continue;
            lis[++top]=atan2l(x[j]-x[i],y[j]-y[i]);
            if(lis[top]<0) lis[top]+=pi*2;
        }
        sort(lis+1,lis+top+1);
        for(j=1;j<=top;j++) lis[j+top]=lis[j]+pi*2;
        for(j=1,k=1;j<=top;j++){
            while(k<=top*2&&(lis[k]<lis[j]+pi)) k++;
            l=k-j-1;r=n-1-l-1;
            ans+=(1ll*l*(l-1)/2ll+1ll*r*(r-1)/2ll-1ll*l*r)*2ll;
        }
    }
    cout<<ans<<'\n';
}

猜你喜欢

转载自www.cnblogs.com/dedicatus545/p/10479494.html