【YBT2023寒假Day15 B】堵命运枪(计算几何)(Pick定理)
堵命运枪
题目链接:YBT2023寒假Day15 B
题目大意
给你一个凸多边形,保证没有三点贡献,随机一个至少包含三个点的点集,问你这些点形成的新凸多边形中,严格在这个凸多边形中的点数的期望。
思路
首先我们考虑给你一个多边形要怎么算点数。
有个叫 Pick 定理的东西,简单格点多边形的面积等于边上的点数\(/2+\)内部点数\(-1\)。
那内部点数就是:多边形面积\(+1-\)边上的点数\(/2\)。
多边形的面积我们就直接用叉积除二那个求。
边上的点数也不难因为给你的点都在整点上,就直接是横纵坐标差的 \(\gcd\) 就对了。
接下来考虑子集。
首先不如算每个情况的答案和,再除以总数 \(2^n-1-n-\binom{n}{2}\)。
那我们考虑对于每条边统计贡献,因为你注意到每个边贡献的方式都只跟自己有关。
那考虑要怎样才能贡献,除了选它这两个点。
那就是至少还要选一个点,而且不能两侧都选。
那我们为了便于统计,我们只选左边的,那如果左侧 \(a\) 个点,右侧 \(b\) 个点(右侧包括这两个点)。
概率是 \((1-2^{-a})2^{-b}\)。
那你每个贡献都乘上概率就是了。
但是还有一个问题就是你枚举点对是 \(O(n^2)\) 的。
不过注意到它这里有精度,而且你右侧点越多,概率越小,那我们到很小很小的时候,概率就忽略不计了,那我们只用枚举大概 \(60\) 个旁边的点就差不多了。
代码
#include<cstdio>
using namespace std;
const int N = 1e5 + 100;
struct node {
double x, y;
}a[N];
int n;
double two[N], sum, outsum, line;
node operator +(node x, node y) {return (node){x.x + y.x, x.y + y.y};}
node operator -(node x, node y) {return (node){x.x - y.x, x.y - y.y};}
double operator *(node x, node y) {return x.x * y.x + x.y * y.y;}
double operator ^(node x, node y) {return x.x * y.y - x.y * y.x;}
int abs(int x) {return x < 0 ? -x : x;}
int gcd(int x, int y) {
if (!y) return x;
return gcd(y, x % y);
}
double P(int a) {//右侧(包含端点)
return (two[a] - two[n]) / (1.0 - two[n] * (1.0 + 1.0 * n + 1.0 * n * (n - 1) / 2.0));
}
int main() {
freopen("ak.in", "r", stdin);
freopen("ak.out", "w", stdout);
scanf("%d", &n);
for (int i = 0; i < n; i++) scanf("%lf %lf", &a[i].x, &a[i].y);
two[0] = 1; for (int i = 1; i < N; i++) two[i] = two[i - 1] / 2.0;
for (int i = 2; i < n; i++) sum += ((a[i - 1] - a[0]) ^ (a[i] - a[0])) / 2.0;
for (int l = 0; l < n; l++) {
double now = 0;
for (int sz = 1; sz < n && sz <= 60; sz++) {
int r = (l + sz) % n;
now += ((a[(r - 1 + n) % n] - a[l]) ^ (a[r] - a[l])) / 2.0;//统计在新多边形外面的面积
outsum += P(sz + 1) * now; line += P(sz + 1) * gcd(abs(a[l].x - a[r].x), abs(a[l].y - a[r].y)) / 2.0;
}
}
printf("%.12lf", sum - outsum - line + 1.0);
return 0;
}