初见安~这里是传送门:Codeforces 1284 E
Sol
一眼计算几何,然后就不可做了【啪。
题意是这样的:平面上有n个点,问你存在多少组四个点围成的四边形【也可能是三角形】严格包围某个点的情况。不存在三点共线。
【思路、代码参考:https://www.bilibili.com/video/av82161298?p=4】
很容易发现,我们不管事考虑每个点有多少个四边形包含还是每个四边形包含多少个点,都是个比较难整的问题。【听说可以用扫描线?忘了怎么写了。】所以我们不妨倒过来考虑:有多少种情况是不合法的,也就是考虑四边形不能包含点的情况。选择点的总数是,这个不必多说。
题目数据范围支持,
明显在暗示要枚举每个点。
我们这么看:我们枚举每个点, 找有多少个四边形是不能包含它的。
可以随手画一个看看,我们将这个点放在直角坐标系的原点位置。
很明显,如果另外四个点都在同一个象限,是肯定不会包含它的。那么在同一个平面内呢?
很明显也是满足的,不会经过原点。如果四个点中有一个到三个点到了y的负半轴,那么就很有可能会过原点了。
但是你可以说:如果四个点都在y的负半轴,那不是也满足条件吗?
所以我们判断的条件可以完善为:四个点与原点连线的最大夹角不超过180度,那么这四个点连成的四边形就不会经过原点。
所谓最大夹角:
就是图中的。没有超过180度,所以这四个点的连线明显不会经过原点。
那么怎么找这样的四个点呢?我们可以固定其中一个点,然后顺时针延展180度以内,看看这中间有多少个点被包含进来,然后随便选三个即可。所以题目保证了没有三点共线就是非常好的一个条件,大大简化了我们的过程。
最后梳理一下计算过程:枚举每个点,找不包含这个点的四边形,也就是处理出这个点以外的所有点的角度。我们可以用到一个函数叫做【注意里面是浮点类型!!!】表示求出坐标为(a,b)的点和原点的坐标夹角【注意是弧度制】。排个序,顺时针扫每个点作为最右端【或者说是弧度最小的那个点】的时候的情况,看看有多少个点可以包含进弧度π以内,随便选三个即可。
是不是特别的巧妙!!!!!!!
但是这个题有点坑【有可能是计算几何共有的坑】就是精度问题。你不开long double就一直WA9,还有求ans的时候很容易就爆int……
上代码——
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#define maxn 3000
using namespace std;
typedef long long ll;
const long double pai = acos(-1.0L);//注意1.0后面有个L,这个影响精度的很重要
int read() {
int x = 0, f = 1, ch = getchar();
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
return x * f;
}
int n;
long double x[maxn], y[maxn];
vector<long double> v;
signed main() {
n = read();
for(int i = 0; i < n; i++) scanf("%Lf%Lf", &x[i], &y[i]);
ll ans = 1ll * n * (n - 1) * (n - 2) * (n - 3) * (n - 4) / 24;//C_n^5,所有情况数
for(int i = 0; i < n; i++) {
v.clear();
for(int j = 0; j < n; j++) if(i != j) v.push_back(atan2(x[j] - x[i], y[j] - y[i]));//放进每个点的角度
sort(v.begin(), v.end());//排个序
register int now = 0;//用now线性扫描,可以降低复杂度的
ll cnt;
for(int j = 0; j < n - 1; j++) {
while(now < j + n - 1) {//因为平面是环形的,所以用延长一倍的技巧
long double tmp = v[now % (n - 1)] - v[j];//这才是这个点相对点j的实际角度
if(tmp < 0) tmp += 2 * pai;//多转一圈好判定
if(tmp < pai) now++;
else break;//大于π了明显不合法了
}
cnt = (now - j - 1);//这里的边界手枚一下就很好理解的
ans -= cnt * (cnt - 1) * (cnt - 2) / 6;//cnt一定要开Long long!!这很重要
}
}
printf("%lld\n", ans);
return 0;
}
呼……这就算平面几何入门了吧。就像数论随手开long long一样,要随手开long double……
迎评:)
——End——