题解:AT_abc136_f [ABC136F] Enclosed Points

传送门

Solution

对于一个点 \(i\),我们将其与其它点匹配,故有 \(2^{n - 1}\) 的方案数,这是答案的初始。

对于每个点 \((x_i,y_i)\) 再建系,四个象限都可能会有点,我们此时考虑四个象限的点如何匹配,才能使 \((x_i,y_i)\) 包含其中,稍微手玩一下就可以发现,对于一四象限、二三象限的点匹配,是无法包含 \((x_i,y_i)\) 的,但是一三象限、二四象限就可以,考虑统计答案。

分别假设四个象限各有 \(cnt_1,cnt_2,cnt_3,cnt_4\) 个点:

  • 一三象限的答案:

    \[(2^{cnt_1} - 1) \times (2^{cnt_3} - 1) \]

  • 二四象限的答案:

    \[(2^{cnt_2} - 1) \times (2^{cnt_4} - 1) \]

然而这样是在其余两个象限都没有点存在的情况下,对于其余象限的所有点,任意选择或不选择都对答案不影响,故:

  • 一三象限的答案:

    \[(2^{cnt_1} - 1) \times (2^{cnt_3} - 1) \times 2^{cnt_2 + cnt_4} \]

  • 二四象限的答案:

    \[(2^{cnt_2} - 1) \times (2^{cnt_4} - 1) \times 2^{cnt_1 + cnt_3} \]

显然的,这样计算肯定会算重某些情况,考虑容斥。

我们对选择一三象限作限制,钦定其只能同时再选二或四象限,这样一来,选择二四象限时,可以任意选一三象限:

  • 一三象限的答案:

    \[(2^{cnt_1} - 1) \times (2^{cnt_3} - 1) \times 2^{cnt_2} + (2^{cnt_1} - 1) \times (2^{cnt_3} - 1) \times 2^{cnt_4} \]

  • 二四象限的答案:

    \[(2^{cnt_2} - 1) \times (2^{cnt_4} - 1) \times 2^{cnt_1 + cnt_3} \]

这样就可以避免选重的情况,现在考虑如何维护这个东西。

这里选用线段树维护,分别维护当前点左侧、右侧点数,总复杂度 \(O(n \log n)\)

Code

#include <bits/stdc++.h>
#define int long long

using namespace std;
namespace FASTIO {
    inline int read() {
        int res = 0, f = 1;
        char ch = getchar();
        while (!isdigit (ch)) f = ch == '-' ? -1 : 1, ch = getchar();
        while (isdigit (ch)) res = (res << 1) + (res << 3) + (ch ^ 48), ch = getchar();
        return res * f;
    }
    inline void write (int x) {
        int st[33], top = 0;
        do { st[top ++] = x % 10, x /= 10; } while (x);
        while (top) putchar (st[-- top] + '0');
    }
};
using namespace FASTIO;
const int MAXN = 2e5 + 10, MOD = 998244353;
int n, cpy[MAXN], Answer, pw2[MAXN];
struct Ayaka { int x, y; } pt[MAXN];
inline bool Cmp (Ayaka s1, Ayaka s2) { return s1.x < s2.x; }
struct Segmentree { int l, r, appTim; } seg[2][MAXN << 2]; /* appTim 表示点出现的次数,即象限内点数 */

/* seg[0] 的一维维护当前点左侧点数,seg[1] 的一维维护右侧 */

inline void pushup (int rt, int p) { seg[p][rt].appTim = seg[p][rt << 1].appTim + seg[p][rt << 1 | 1].appTim; }

void Build (int l, int r, int rt, int p) {
    seg[p][rt].l = l, seg[p][rt].r = r, seg[p][rt].appTim = 0;
    if (l == r) return;
    int mid = l + r >> 1;
    Build (l, mid, rt << 1, p), Build (mid + 1, r, rt << 1 | 1, p);
    pushup (rt, p);
}

void update (int rt, int p, int pos, int k) {
    int l = seg[p][rt].l, r = seg[p][rt].r;
    if (l == r) {
        seg[p][rt].appTim += k;
        return;
    }
    int mid = l + r >> 1;
    if (pos <= mid)
        update (rt << 1, p, pos, k);
    else
        update (rt << 1 | 1, p, pos, k);
    pushup (rt, p);
}

int query (int ql, int qr, int rt, int p) {
    int l = seg[p][rt].l, r = seg[p][rt].r;
    if (ql <= l && qr >= r)
        return seg[p][rt].appTim;
    int mid = l + r >> 1, tmpAns = 0;
    if (ql <= mid)
        tmpAns = (tmpAns + query (ql, qr, rt << 1, p)) % MOD;
    if (qr > mid)
        tmpAns = (tmpAns + query (ql, qr, rt << 1 | 1, p)) % MOD;
    return tmpAns;
}

inline void Getpw2() {
    pw2[0] = 1;
    for (int i = 1; i < MAXN; i ++)
        pw2[i] = (pw2[i - 1] << 1) % MOD;
    return;
}

signed main() {
    n = read(), Getpw2();
    for (int i = 1; i <= n; i ++)
        pt[i].x = read(), pt[i].y = read(), cpy[i] = pt[i].y;
    sort (cpy + 1, cpy + n + 1), sort (pt + 1, pt + n + 1, Cmp);
    /* 将点按照 x 坐标排序 */
    Build (1, MAXN - 1, 1, 0), Build (1, MAXN - 1, 1, 1);
    for (int i = 1; i <= n; i ++) {
        int ptr = lower_bound (cpy + 1, cpy + n + 1, pt[i].y) - cpy;
        update (1, 1, ptr, 1);
        /* 先将所有点插入到当前点右侧 */
    }
    for (int i = 1; i <= n; i ++) {
        int ptr = lower_bound (cpy + 1, cpy + n + 1, pt[i].y) - cpy;
        update (1, 1, ptr, -1);
        /* 从右侧删除当前点 */
        int upL = query (1, ptr - 1, 1, 0), downL = query (1, ptr - 1, 1, 1); 
        /* 左下左上的点数 */
        int upR = query (ptr + 1, MAXN - 1, 1, 0), downR = query (ptr + 1, MAXN - 1, 1, 1);
        /* 右下右上的点数 */
        int tmpAnsL = (pw2[upL] - 1) * (pw2[downR] - 1) % MOD, tmpAnsR = (pw2[downL] - 1) * (pw2[upR] - 1) % MOD;
        Answer = ((Answer + tmpAnsL) % MOD + tmpAnsR) % MOD;
        if (downL) Answer = (Answer + tmpAnsL * (pw2[downL] - 1) % MOD) % MOD;
        /* 若左下存在点,新增答案 */
        if (upR) Answer = (Answer + tmpAnsL * (pw2[upR] - 1) % MOD) % MOD;
        /* 若右上存在点,新增答案 */
        Answer = (Answer + tmpAnsR * (pw2[upL + downR] - 1) % MOD) % MOD;
        /* 对于无限制的左上右下再次新增答案 */
        Answer = (Answer + pw2[n - 1]) % MOD;
        /* 一开始任意匹配的答案 */
        update (1, 0, ptr, 1);
        /* 向左侧插入当前点 */
    }
    write (Answer), putchar ('\n');
    return 0;
}
posted @ 2025-01-14 16:02  xAlec  阅读(21)  评论(0)    收藏  举报