• 【题解】洛谷P4688 [Ynoi2016] 掉进兔子洞


    原题链接

    一些题外话

    这是我做的第一道 Ynoi,做完这题后我深深感受到了 lxl 题目的毒瘤。

    做题时曾受神犇 rui_er 题解的启发。

    题意

    给定一个长为 $n$ 的序列 $a$

    有 $m$ 个询问,每次询问三个区间,把三个区间中同时出现的数一个一个删掉,问最后三个区间剩下的数的个数和,询问独立。

    注意这里删掉指的是一个一个删,不是把等于这个值的数直接删完,比如三个区间是 $[1,2,2,3,3,3,3]$,$[1,2,2,3,3,3,3]$ 与 $[1,1,2,3,3]$,就一起扔掉了 $1$ 个 $1$,$1$ 个 $2$,$2$ 个 $3$。

    数据范围:$1\leq n , m \leq 10^5$,$1 \leq a_i\leq 10^9$,$1\leq l_1,r_1,l_2,r_2,l_3,r_3\leq n$,$l_1\leq r_1$,$l_2\leq r_2$,$l_3\leq r_3$。

    时间限制:$3.0$ 秒

    空间限制:$512\text{MiB}$

    题解

    首先 $a_i \le 10^9$,但是我们只在意 $a_i$ 的种类,因此先将其离散化。

    显然我们不必同时求出这 $3$ 个区间内的所有数字。我们可以将 $3 \times m$ 个区间顺序打乱,以较快的速度将这堆区间一并求出。

    看到这里,应该都知道这时用莫队来解决问题就可以了。至于每个区间的答案,我们可以用 bitset 存储,

    我们可以先将三个区间的长度和相加,那么对于每个询问,答案就是三个区间的长度和,减去 $3$ 倍的三个可重集的交集

    如果每个子区间内所有元素互不相等,那这很好办,我们把数字变成下标,放入一个 bitset 内,求交集时直接做与运算,时间复杂度是 $\mathcal{O}(\frac{nm}{ω})$

    但是实际上元素会重复,我们需要想出一种新的办法,将每个数字映射到 bitset 内,使得每个数字都可能被记录到。

    发现相同的元素不收到插入顺序的影响。因此有一种映射的办法,举例如下:

    • 先将元素排序,例如 $[1,2,2,3,3,3,3]$。
    • 记录每个不同元素的初始位置:$1; \ 2; \ 4$。
    • 现在我们记录数字 $1$,此时在 bitset 中就在第 $1$ 个位置标记。
    • 之后记录数字 $2$,此时在 bitset 中就在第 $2$ 个位置标记。
    • 接着记录数字 $3$,此时在 bitset 中就在第 $4$ 个位置标记(注意第 $3$ 个位置是 $2$ 的地盘,不能碰)。
    • 接下来,我们再记一个 $3$。注意到第 $4$ 个位置被占据了,且 bitset 中之前存了 $1$ 次 $3$,那么这次将 $3$ 存入位置 $4+1=5$。
    • 最后如果再插入一个 $3$,因为之前 $3$ 出现了 $2$ 次,所以这次存入位置 $4+2=6$。

    流程大概就是这样,莫队算法过程中删除数字的步骤也同理。

    我们得到了每个区间的 bitset,就可以拿来取交集了。这个步骤复杂度是有保证的,为 $\frac{n}{ω}$,$ω$ 为 $32$ 或 $64$。

    但是还剩了一个问题——记录所有询问的 bitset  需要空间为 $\frac{n \times m}{8}$ 字节,存不下!

    这时我们就需要考虑重复利用 bitset 了。观察到不同的询问不会互相干扰,因此我们可以将 $m$ 个询问分成 $S$ 个小组,每小组询问分别跑一次莫队。这里 $S=3$ 或 $4$ 就可以了。

    这样就解决了空间不够的问题。假设不考虑分组而浪费的时间,时间复杂度为 $\mathcal{O}(\frac{nm}{ω}+m \sqrt{n})$,$ω$ 为 $32$ 或 $64$。

    实现代码

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int INF = 1e9, N = 100010;
    
    struct S{
        int l, r, id, stp;
    }s[N];
    int n, M, a[N], bb[N], cnt, fir[N], sz[N], siz, SZ, blk[N], ans[N], len[N], vis[N];
    bitset <100010> b[20010], now, res;
    
    void add(int x){
        x = a[x];
        now[fir[x] + sz[x]] = 1;
        sz[x]++;
    }
    void del(int x){
        x = a[x];
        now[fir[x] + sz[x] - 1] = 0;
        sz[x]--;
    }
    
    bool cmp(S x, S y){
        if(blk[x.l] == blk[y.l]) return x.r > y.r;
        return blk[x.l] < blk[y.l];
    }
    
    void solve(int m){
        for(int i = 1; i <= m; i++){
            scanf("%d%d%d%d%d%d", &s[i * 3 - 2].l, &s[i * 3 - 2].r, &s[i * 3 - 1].l, &s[i * 3 - 1].r, &s[i * 3].l, &s[i * 3].r);
            s[i * 3 - 2].id = i, s[i * 3 - 2].stp = 0;
            s[i * 3 - 1].id = i, s[i * 3 - 1].stp = 1;
            s[i * 3].id = i, s[i * 3].stp = 2;
        }
        now.reset(), siz = (int)sqrt(m * 3);
        for(int i = 1; i <= m * 3; i++) len[i] = vis[i] = 0, blk[i] = (i - 1) / siz + 1;
        for(int i = 1; i <= M; i++) sz[i] = 0;
        
        sort(s + 1, s + m * 3 + 1, cmp);
        
        for(int i = 1, L = 1, R = 0; i <= 3 * m; i++){
            while(L > s[i].l) L--, add(L);
            while(R < s[i].r) R++, add(R);
            while(L < s[i].l) del(L), L++;
            while(R > s[i].r) del(R), R--;
            if(!vis[s[i].id]) b[s[i].id] = now;  // 这里不分开记 3 个区间的 bitset,而是记住初始编号,之后在同一个 bitset 内与运算,节省空间
            else b[s[i].id] &= now;
            vis[s[i].id] = 1;
            len[s[i].id] += s[i].r - s[i].l + 1;
        }
        
        for(int i = 1; i <= m; i++)
            printf("%d\n", len[i] - 3 * b[i].count());
    }
    
    int main(){
    
        int m;
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; i++)
            scanf("%d", &a[i]), bb[i] = a[i];
        sort(bb + 1, bb + n + 1);
        for(int i = 1; i <= n; i++)
            if(bb[i] != bb[i - 1])
                fir[++cnt] = i;
        M = unique(bb + 1, bb + n + 1) - bb - 1;
        for(int i = 1; i <= n; i++)
            a[i] = lower_bound(bb + 1, bb + M + 1, a[i]) - bb;
        while(m >= 1)
            solve(m > 20000 ? 20000 : m), m -= 20000;  // 这里最多分 5 组处理
    
        return 0;
    }
  • 相关阅读:
    HDU 1160 dp中的路径问题
    zzuli 1907: 小火山的宝藏收益
    POJ 3414 dfs广搜直接应用
    http://acm.zzuli.edu.cn/zzuliacm/problem.php?cid=1158&pid=5 二分函数的间接应用
    LightOJ 1067 组合数取模
    九段美到极致的句子
    质数和分解
    codevs 1080 线段树练习
    codevs 2806 红与黑
    codevs 2152 滑雪
  • 原文地址:https://www.cnblogs.com/zengpeichen/p/15674301.html
Copyright © 2020-2023  润新知