BZOJ4939[Ynoi2016]掉进兔子洞(莫队+bitset)

本文分享了一种使用bitset维护可重复元素集合的创新方法,适用于解决“几个集合共同含有的元素”问题,并通过实例详细解析了如何在空间受限的情况下,采用分组处理询问策略优化算法效率。

题目链接

BZOJ

洛谷

扯点别的

听说这是比较亲民的一道Ynoi,于是我就去摸了一下,于是一个晚上就没了……不过至少还是学到了\(bitset\)维护可重集合的方法,以及当空间开不下时分组处理询问的花操作……

解析

大佬们说容易想到bitset,那就想到bitset吧……

这种形如“几个集合共同含有的元素”的问题容易让人想\(bitset\)搞一搞,但是我们发现裸的\(bitset\)只能表示有没有,不能表示有多少,下面就是神奇的让\(bitset\)能表示有多少的方法

就这道题而言首先肯定是离散化,但是离散化时我们不去重,这样相同元素会是连续的一段,我们取第一个出现的位置作为离散化后的值,简单举个例子:1,4,2,4,4,5 => hash[1] = 0,hash[2] = 1,hash[4] = 2,hash[5] = 5

假设我们要加入一个数\(x\),它之前已经在这个集合中出现了\(cnt\)次,那么我们把\(x + cnt\)那一位置为\(1\),删除同理,即:bitset中第\(i + j\)个位置表示数字\(i\)\(j + 1\)次出现

这样当我们把每个\(bitset\)与起来就相当于对每个数的出现次数取了个\(min\)

回到题目,假设我们知道了三个区间的\(bitset\)与起来是\(S\),那么答案显然就是\(len1 + len2 + len3 - |S| \times 3\)

我们发现\(bitset\)加入和删除都能很快处理,询问的又是区间,那么就可以上莫队试试了,莫队的时候维护一下每个数出现的次数即可

还有一点,直接开\(1e5\)\(bitset\)肯定开不下,于是我们把询问分组,每组上一次莫队即可

分组大小可以算算,我比较懒就从\(1e4\)开始试然后到\(3e4\)的时候就过了……

代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <bitset>
#include <algorithm>
#define MAXN 100003
#define belong(x) ((x) / per_block)

typedef long long LL;
struct Query {
    int l, r, id;
} qry[90005];
int arr[MAXN], hash[MAXN], cnt[MAXN], N, M, per_block, idx;
std::bitset<MAXN> cur, ans[30003];
bool vis[30003];

bool operator <(const Query &q1, const Query &q2) {
    return belong(q1.l) == belong(q2.l) ? q1.r < q2.r : belong(q1.l) < belong(q2.l);
}
bool cmp(const Query &q1, const Query &q2) { return q1.id < q2.id; }

int main() {
    scanf("%d%d", &N, &M);
    for (int i = 0; i < N; ++i) scanf("%d", arr + i), hash[i] = arr[i];
    std::sort(hash, hash + N);
    for (int i = 0; i < N; ++i)
        arr[i] = std::lower_bound(hash, hash + N, arr[i]) - hash;
    while (per_block * per_block < N) ++per_block;

    while (M) {
        memset(cnt, 0, sizeof cnt);
        memset(vis, 0, sizeof vis);
        cur.reset();
        int tot = std::min(30000, M);
        for (int k = 0; k < tot * 3; ++k) scanf("%d%d", &qry[k].l, &qry[k].r), qry[k].id = k / 3;
        std::sort(qry, qry + tot * 3);
        for (int l = 0, r = -1, i = 0; i < tot * 3; ++i) {
            --qry[i].l, --qry[i].r;
            while (r < qry[i].r) ++r, cur[arr[r] + cnt[arr[r]]] = 1, ++cnt[arr[r]];
            while (r > qry[i].r) --cnt[arr[r]], cur[arr[r] + cnt[arr[r]]] = 0, --r;
            while (l < qry[i].l) --cnt[arr[l]], cur[arr[l] + cnt[arr[l]]] = 0, ++l;
            while (l > qry[i].l) --l, cur[arr[l] + cnt[arr[l]]] = 1, ++cnt[arr[l]];
            if (!vis[qry[i].id]) vis[qry[i].id] = 1, ans[qry[i].id] = cur;
            else ans[qry[i].id] &= cur;
        }
        std::sort(qry, qry + tot * 3, cmp);
        for (int i = 0; i < tot; ++i) {
            int p1 = i * 3, p2 = p1 + 1, p3 = p2 + 1;
            printf("%d\n", qry[p1].r - qry[p1].l + qry[p2].r - qry[p2].l + qry[p3].r - qry[p3].l + 3 - (int)ans[i].count() * 3);
        }
        M -= tot;
    }

    return 0;
}
//Rhein_E

转载于:https://www.cnblogs.com/Rhein-E/p/10506971.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值