AtCoder ARC082 E Convex Score 贡献思想 双射

来源:互联网 发布:linux 多线程 read 编辑:程序博客网 时间:2024/06/06 01:14

题目链接

题意

给定 N 个点,对于一个凸 n 边形,称其的 n 个顶点构成一个集合 S,并且这个多边形内及其边上有 k 个顶点,定义这个 Sscore2kn.

对所有的 score 求和,输出 mod 998244353 的值。

思路

考虑一个凸 n 边形,其内与其上共 k 个顶点,那么它对最后答案的贡献是 2kn。这意味着什么呢?就是除去顶点以外的 kn 个顶点取不取的所有情况数。

0 个,取 1 个,取若干个,取所有,事实上这些所有情况的点的效果都是那一个凸 n 边形(因为已经固定取了那 n 个点作为顶点)。

所以可以说,对于任意 m 个点,只要其中有一个子集能构成一个凸多边形,那么其对最后答案就有贡献为。

贡献为多少呢?我们猜想是 1。所以究竟可不可能重复呢?是不可能的。因为对于给定的 m 个点,其最外面的一圈轮廓(…)是确定下来的,而顶点的 n 个点也随之确定。

于是就得到了一个双射,问题就转化成了,有多少个点集满足其子集能构成凸多边形,也即有多少个点集不是所有点共线。

这里有两种做法。

法一:暴力

复杂度:O(n3)

枚举两个顶点,看有多少个点在这两个顶点连成的线上。枚举的两个点固定,其他所有点中至少取一个,情况数就是这条线上共线的点集个数。显然,不会有重复。累和,即为最终答案。

Code

#include <bits/stdc++.h>#define maxn 100010typedef long long LL;const LL mod = 998244353;LL x[maxn], y[maxn], P[maxn];int main() {    LL n;    scanf("%lld", &n);    for (int i = 0; i < n; ++i) scanf("%lld%lld", &x[i], &y[i]);    P[0] = 1;    for (int i = 1; i <= n; ++i) (P[i] = P[i-1] << 1) %= mod;    LL ans = P[n] - 1 - n - n*(n-1)/2;    for (int i = 0; i < n; ++i) {        for (int j = i+1; j < n; ++j) {            int cnt = 0;            for (int k = j+1; k < n; ++k) {                if ((x[j]-x[i]) * (y[k]-y[j]) == (x[k]-x[j]) * (y[j]-y[i])) ++cnt;            }            ((ans -= P[cnt]-1) += mod) %= mod;        }    }    printf("%lld\n", ans);    return 0;}

法二:并查集

复杂度:O(n2logn)

参考:[AtCoder]AtCoder Regular Contest 082 CDEF ——jstztzy

十分巧妙的方法,先预处理出两两点对之间的线段,再将线段排个序,那么所有共线的线段肯定都排在了一起并且具有相同的属性(原博是用的hash,我就直接比较了比例)。

那么对于这一些线段,对其中的任一条线段,将其两个端点 union 一下,如果是共线的线段,最后所有的顶点肯定都会在同一个 set 里面。

Code

#include <bits/stdc++.h>#define maxn 100010typedef long long LL;using namespace std;const LL mod = 998244353;LL x[maxn], y[maxn], P[maxn];int fa[maxn], sz[maxn];int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }void unionn(int x, int y) {    x = find(x), y = find(y);    if (x == y) return;    if (sz[x] > sz[y]) swap(x, y);    fa[x] = y; sz[y] += sz[x];}struct node { int p, q; LL dx, dy; };bool cmp(node u, node v) { return u.dx < v.dx || (u.dx == v.dx && u.dy < v.dy); }vector<node> seg;LL GCD(LL x, LL y) { return y ? GCD(y, x % y) : x; }int main() {    LL n;    scanf("%lld", &n);    for (int i = 0; i < n; ++i) scanf("%lld%lld", &x[i], &y[i]);    P[0] = 1;    for (int i = 1; i <= n; ++i) (P[i] = P[i-1] << 1) %= mod;    for (int i = 0; i < n; ++i) {        for (int j = i+1; j < n; ++j) {            LL dx = x[i]-x[j], dy = y[i]-y[j];            LL gcd = GCD(dx, dy);            dx /= gcd, dy /= gcd;            if (dx < 0) dx = -dx, dy = -dy;            seg.push_back(node{i, j, dx, dy});        }    }    sort(seg.begin(), seg.end(), cmp);    LL ans = P[n] - 1 - n;    int i = 0, j = 0;    for (; i < seg.size(); i = j+1) {        for (int k = 0; k < n; ++k) fa[k] = k, sz[k] = 1;        for (j = i; ; ++j) {            unionn(seg[j].p, seg[j].q);            if (j+1 >= seg.size() || seg[i].dx != seg[j+1].dx || seg[i].dy != seg[j+1].dy) break;        }        for (int k = 0; k < n; ++k) {            if (fa[k] == k) ((ans -= (P[sz[k]] - sz[k] - 1)) += mod) %= mod;        }    }    printf("%lld\n", ans);    return 0;}

// 可能是我写ci了,暴力的跑了 3ms,并查集跑了 26ms