洛谷P5816 [CQOI2010]内部白点(扫描线/主席树)

本文分享了如何使用主席树优化求解P5816[CQOI2010]内部白点问题,通过构建双线性时间复杂度的数据结构,计算在特定区间内哪些点可能变黑,最终得出可以变为黑点的点总数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

P5816 [CQOI2010]内部白点

题目链接

在这里插入图片描述
扫描线的做法这里不过多赘述了,挺多题解都讲的挺不辍的。这里分享一个主席树的写法。

首先我们明确的是,第一步变化之后,整个图就不会变了。为什么呢,第二步才改变的点 q q q 肯定是因为第一步改变的某个点 p p p 或者好几个点变成黑点才改变的。如果 q q q 因为 p p p 而改变且 p p p q q q 的水平左侧,那么 p p p 左侧肯定还有点,因为 p p p 是第一步变成黑点的点,那么在此情况下, q q q 第一步也应当变成黑点,不应该等到第二次才变化。其他几个方向或者多个点的情况是同理的。也就是说第二步整个图就不会发生变化了,因此以后永远也不会再变化。

那么会从白变黑的点肯定是夹在两个 y y y 相等的相邻点中间的点,现将这两个相邻点表示为 ( x 1 , y ) (x_1, y) (x1,y) ( x 2 , y ) (x_2, y) (x2,y) ,那么我们只要对每一对这样的相邻点去求答案最后加和就可以了。那么其中间的点可以表示为 { ( x , y )    ∣   x ∈ [ x 1 + 1 , x 2 − 1 ] } \{(x,y)~~|~x∈[x_1+1,x_2-1]\} {(x,y)   x[x1+1,x21]} ,那么这里有多少个点可以变成黑点其实也就是对于这里的每个 x x x ,去看是否其上下分别至少都存在一个点,但是我们发现这样不好计算,于是我们反过来算,我们算有多少个不满足条件。不满足条件就是,对于 x x x 这一列来说,要么只有 y y y 的上方没有点,要么只有下侧没有点,要么都没。那么我就想:

①以纵轴为时间轴建立主席树,可以算出 y y y 上方在 [ x 1 + 1 , x 2 − 1 ] [x_1+1,x_2-1] [x1+1,x21] 内有多少种 x x x ,那么用总的种类数减一下,也就可以算出缺了多少种 x x x ,也就算出了 y y y 的上方有多少纵列是空的。

②再以纵轴为时间轴反向建立主席树,就可以得到 y y y 的下方在 [ x 1 + 1 , x 2 − 1 ] [x_1+1,x_2-1] [x1+1,x21] 内有多少种 x x x,同理也可计算出 y y y 下方有多少纵列是空的。

③然后我们用正向建立还是反向建立的主席树都可得到对于全局在 [ x 1 + 1 , x 2 − 1 ] [x_1+1,x_2-1] [x1+1,x21] 内有多少种 x x x ,同理算出全局 [ x 1 + 1 , x 2 − 1 ] [x_1+1,x_2-1] [x1+1,x21] 内有多少纵列是空的。

这里我们将前两项相加减去第三项即可算出 [ x 1 + 1 , x 2 − 1 ] [x_1+1,x_2-1] [x1+1,x21] 内,有多少纵列要么 y y y 上面是空的,要么 y y y 下面是空的,要么 y y y 的上下全是空的。说白了我们就算出了无法变成黑点的情况,那么减一下,不就变成了可以变成黑点的情况。

注意这里用主席树没用使用区间求值的操作,因为种类数不满足前缀相减的性质,所以这里正反建了两颗主席树。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 1e5 + 10;

int n;

int tree[2][N << 5], lson[2][N << 5], rson[2][N << 5], root[2][N], tot[2];

int ytot;

pair<int, int> p[N];

vector<int> g[N];

void update(int p, int pre, int &rt, int l, int r, int x) { 
    rt = ++tot[p];
    tree[p][rt] = tree[p][pre];
    lson[p][rt] = lson[p][pre];
    rson[p][rt] = rson[p][pre];
    if (l == r) {
        if (!tree[p][rt]) tree[p][rt] = 1;
        return ;
    }
    int mid = l + r >> 1;
    if (x <= mid) update(p, lson[p][pre], lson[p][rt], l, mid, x);
    else update(p, rson[p][pre], rson[p][rt], mid + 1, r, x);
    tree[p][rt] = tree[p][lson[p][rt]] + tree[p][rson[p][rt]];
}

int query(int p, int rt, int l, int r, int L, int R) {
    if (L <= l && r <= R) {
        return tree[p][rt];
    }
    int mid = l + r >> 1, sum = 0;
    if (mid >= L) sum = query(p, lson[p][rt], l, mid, L, R);
    if (mid < R) sum += query(p, rson[p][rt], mid + 1, r, L, R);
    return sum;
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
#endif
    scanf("%d", &n);
    ll ans = n;
    for (int i = 1; i <= n; ++i) {
        scanf("%d%d", &p[i].second, &p[i].first);
    }
    sort(p + 1, p + 1 + n);
    p[0].first = 2e9, p[0].second = 2e9;
    for (int i = 1; i <= n; ++i) {
        if (p[i].first == p[i - 1].first) {
            update(0, root[0][ytot], root[0][ytot], -1e9, 1e9, p[i].second);
            g[ytot].push_back(p[i].second);
        }
        else {
            update(0, root[0][ytot], root[0][ytot + 1], -1e9, 1e9, p[i].second);
            g[++ytot].push_back(p[i].second);
        }
    }
    
    p[n + 1].first = 2e9, p[n + 1].second = 2e9, ytot = 0;
    for (int i = n; i >= 1; --i) {
        if (p[i].first == p[i + 1].first) {
            update(1, root[1][ytot], root[1][ytot], -1e9, 1e9, p[i].second);
        }
        else {
            update(1, root[1][ytot], root[1][ytot + 1], -1e9, 1e9, p[i].second);
            ytot++;
        }
    }
    
    for (int i = 1; i <= ytot; ++i) {
        sort(g[i].begin(), g[i].end());
        for (int j = 1; j < (int)g[i].size(); ++j) {
            int L = g[i][j - 1] + 1, R = g[i][j] - 1;
            if (L > R) continue;
            int len = (R - L + 1);
            int up = len - query(0, root[0][i], -1e9, 1e9, L, R);
            int down = len - query(1, root[1][ytot - i + 1], -1e9, 1e9, L, R);
            int all = len - query(0, root[0][ytot], -1e9, 1e9, L, R);
            int white = up + down - all;
            int black = len - white;
            ans += black;
        }
    }
    printf("%lld", ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值