AtCoder ARC082 E Convex Score 贡献思想 双射

题目链接

题意

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

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

思路

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

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

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

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

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

这里有两种做法。

法一:暴力

复杂度: O(n3)

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

Code

#include <bits/stdc++.h>
#define maxn 100010
typedef 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 100010
typedef 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值