【题解】[APIO2010] 信号覆盖
题目分析:
其实就是涉及四个点之间的位置关系,三个点形成圆判断是否包含另一个点。
考虑四个点之间形成的多边形只可能是凸四边形或者是凹四边形,如下图所示:
(上图为凸多边形)
(上图为凹多边形)
因为题目保证不存在四点共圆,也就是说对于任意一个四边形不存在对角之和为 \(180°\),也就是一定存在一组对角其和大于 \(180°\),而另一组小于 \(180°\),所以必然有点满足在另外三点构成的圆上。
如上图凸多边形所示则 \(B\) 一定在 \(ADC\) 所构成的圆上,\(D\) 一定在 \(ABC\) 构成的圆上,因为对角之和大于 \(180°\) 就一定在圆内。
如上图凹多边形所示则 \(C\) 一定在 \(ABD\) 所构成的圆上,原因同理。
所以我们的就可以得到,设 \(x\) 为凸多边形的数量,\(y\) 为凹多边形的数量,答案即:
\[2\times x + y
\]
对于计数而言,显然凹多边形容易计数,因为他有独特的凹角,只要求出凹角的数量就可以得到凹多边形的数量。但是个人习惯而言凸角比凹角好求一些,因为凸角就是小于 \(180°\) 的角。
所以可以考虑对于每一个点极角排序,然后枚举一定含有哪一条边,那么能与这一条边形成一个凸角的边必然在一个连续的区间内,就可以直接使用双指针维护。极角排序如下图所示:
(其实也就是按照逆时针顺序访问每一个点)
所以直接顺着推回去就可以得到答案了。
代码:
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 3005;
struct Point{
int x,y;
}S,a[N],b[N];
int n;
Point operator - (Point a,Point b){return {a.x-b.x,a.y-b.y};}
Point operator + (Point a,Point b){return {a.x+b.x,a.y+b.y};}
int quad(Point a){return (a.x < 0) ^ (3 * (a.y < 0));}
bool cmp(Point a,Point b){
if(quad(a) == quad(b)) return (a.x * b.y - a.y * b.x) > 0;
return quad(a) < quad(b);
}
bool check(Point a,Point b){ //判断 a -> b 夹角是否小于 180°
return a.x * b.y - a.y * b.x > 0;
}
int binom(int n,int m){
if(n < 0 || m < 0 || n < m) return 0;
int ans = 1;
for(int i=n-m+1; i<=n; i++) ans = ans * i;
for(int i=1; i<=m; i++) ans = ans / i;
return ans;
}
int get(Point S){
int tot = 0,ans = 0;
for(int i=1; i<=n; i++){
if(a[i].x == S.x && a[i].y == S.y) continue;
b[++tot] = a[i] - S;
}
sort(b+1,b+tot+1,cmp);
for(int i=1; i<=tot; i++) b[i+tot] = b[i]; //记得开二倍啊啊啊啊啊啊啊
int now = 2;
for(int i=1; i<=tot; i++){
if(now == i) now = i + 1;
while(check(b[i],b[now])) now++;
ans += binom(now - i - 1,2);
}
return ans;
}
signed main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
scanf("%lld",&n);
for(int i=1; i<=n; i++) scanf("%lld%lld",&a[i].x,&a[i].y);
int ans1 = 0;
for(int i=1; i<=n; i++){
S = a[i];
ans1 += get(S); //求凸角的个数
}
int ans2 = 4 * binom(n,4) - ans1; //求凹角/凹多边形个数
int ans3 = binom(n,4) - ans2;//求凸多边形的个数
int ans = ans2 + ans3 * 2;//求答案
printf("%.3f\n",1.0 * ans / binom(n,3) + 3);//别忘了选择的三个点也叫在圆上
return 0;
}