踢罐子 [几何+乱搞]
题面
大概题意:
平面上有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';
}