题解 P4688 [Ynoi2016]掉进兔子洞

这是我的第一道 Ynoi,同时也一发卡到了最优解,发篇题解纪念一下 qwq,不开 $O_2$ 也是能过的。

本题需要的知识点:

- 分块,莫队
- $\text{bitset}$ 优化
- 离散化
- 其它的奇技淫巧

首先明确这题要求的,三个区间内不同时出现的数的个数。列出柿子就是 $(r_1 - l_1 + 1) + (r_2 - l_2 + 1) + (r_3 - l_3 + 1) - rep$,其中 $rep$ 是三个区间内同时出现的数的个数。

首先 $1 \le a_i \le 10^9$,显然离散化。同时注意,本题离散化时不能去重,即不需要 $\text{unique}$,直接使用 $\text{lower}\text{_bound}$ 即可。

随后对每个询问求删除的数,对于这种对多个区间内元素个数的维护我们不难想到莫队,如莫队板子题 小 Z 的袜子 就是这种题型。

老套路,将长度为 $n$ 的序列拆成 $\sqrt n$ 块,把所有询问的区间以 $l$ 为第一关键字,$r$ 为第二关键字排序。

此时考虑如何求出上文的 $rep$。对每个询问开一个长度为 $n$ 的 $\text{bitset}$,每一位是否为 $1$ 即表示这一位是否被删除。再维护另一个 $\text{bitset}$ $Bst$,每一位是否为 $1$ 表示这一区间是否有这个数。则最后要删除的数就是三个区间的 $Bst$ 取交集。

最后要注意,

- 此题 $1 \le n, m \le 10^5$,开不下这么大的 $\text{bitset}$,需要分几次求答案。

- 区间移动位置时,要先加再减。如果先减,则可能会让 $cnt$ 变成负数再变成正数,会导致 $\text{bitset}$ 无效访问内存,从而导致 RE。

于是这题就结束了,$\text{Code}$:

  1 #include <iostream>
  2 #include <cstdio>
  3 #include <bitset>
  4 #include <cstring>
  5 #include <cmath>
  6 #include <algorithm>
  7 #define il inline
  8 #define rg register
  9 
 10 //namespace IO {
 11 //    const int SIZE = (1 << 20) + 1;
 12 //    char ibuf[SIZE], *iS, *iT, obuf[SIZE],*oS = obuf, *oT = obuf + SIZE - 1;
 13 //    char _st[55];
 14 //    int _qr = 0;
 15 //    inline char gc() {
 16 //        return (iS == iT ? iT = (iS = ibuf) + fread(ibuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS++) : *iS++);
 17 //    }
 18 //    inline void qread() {}
 19 //    template<class T1, class ...T2>
 20 //    inline void qread(T1 &IEE, T2&... ls) {
 21 //        register T1 __ = 0, ___ = 1;
 22 //        register char ch;
 23 //        while(!isdigit(ch = gc())) ___ = (ch == '-') ? -___ : ___;
 24 //        do {
 25 //            __ = (__ << 1) + (__ << 3) + (ch ^ 48);
 26 //        }while(isdigit(ch = gc()));
 27 //        __ *= ___;
 28 //        IEE = __;
 29 //        qread(ls...);
 30 //        return ;
 31 //    }
 32 //    inline void flush() {
 33 //        fwrite(obuf, 1, oS - obuf, stdout);
 34 //        oS = obuf;
 35 //        return ;
 36 //    }
 37 //    inline void putc_(char _x) {
 38 //        *oS++ = _x;
 39 //        if(oS == oT) flush();
 40 //    }
 41 //    inline void qwrite() {}
 42 //    template<class T1, class ...T2>
 43 //    inline void qwrite(T1 IEE, T2... ls) {
 44 //        if(!IEE) putc_('0');
 45 //        if(IEE < 0) putc_('-'), IEE = -IEE;
 46 //        while(IEE) _st[++_qr] = IEE % 10 + '0', IEE /= 10;
 47 //        while(_qr) putc_(_st[_qr--]);
 48 //        qwrite(ls...);
 49 //        return ;
 50 //    }
 51 //    struct Flusher_{~Flusher_(){flush();}}io_flusher;
 52 //}
 53 //
 54 //using namespace IO;
 55 //此部分是快读,为防止抄袭注释掉了。
 56 //注 : 此快读板子是@SPFA(uid=177999) 给我的,为防止他被说是 ctj 特来此声明一下。
 57 using namespace std;
 58 
 59 const int N = 1e5 + 5;
 60 const int M = 25005;
 61 
 62 bitset<N> bst[M], Bst;//bitset 开 25000,分四次求出答案。
 63 bool vis[M];
 64 int n, m, tot, blocksize;
 65 int a[N], b[N], blocknum[N];
 66 int ans[N], cnt[N];
 67 
 68 struct Node {
 69     int id, l, r;
 70     bool operator < (const Node &oth) const {
 71         return blocknum[l] == blocknum[oth.l] ? r < oth.r : blocknum[l] < blocknum[oth.l];
 72     }
 73 } Que[N << 2];
 74 
 75 il void Add(int id) {
 76     int x = a[id];
 77     ++cnt[x];
 78     Bst[x + cnt[x] - 1] = 1;     
 79 }
 80 
 81 il void Del(int id) {
 82     int x = a[id];
 83     --cnt[x];
 84     Bst[x + cnt[x]] = 0;
 85 }
 86 
 87 il void Solve(int k) {
 88     tot = 0;
 89     memset(vis, false, sizeof vis);
 90     memset(ans, 0, sizeof ans);
 91     memset(cnt, 0, sizeof cnt);
 92     //因为是分次求出答案,不要忘记每次初始化。
 93     for(rg int i = 1; i <= k; i++) {
 94         for(int j = 1; j <= 3; j++) {
 95             Que[++tot].id = i;
 96             qread(Que[tot].l, Que[tot].r);
 97             ans[i] += Que[tot].r - Que[tot].l + 1;
 98         }
 99     }
100     sort(Que + 1, Que + tot + 1);
101     Bst.reset();
102     int l = 1, r = 0;
103     for(rg int i = 1; i <= tot; i++) {
104         while(l > Que[i].l) Add(--l);
105         while(r < Que[i].r) Add(++r);
106         while(l < Que[i].l) Del(l++);
107         while(r > Que[i].r) Del(r--);
108           //区间移动位置先加再减。
109         if(!vis[Que[i].id]) bst[Que[i].id] = Bst, vis[Que[i].id] = true;//此区间未访问过,直接赋值
110         else bst[Que[i].id] &= Bst;//访问过,取交集
111     }
112     for(rg int i = 1; i <= k; i++) ans[i] -= bst[i].count() * 3;
113     for(rg int i = 1; i <= k; i++) printf("%d\n", ans[i]);
114 }
115 
116 int main() {
117     qread(n, m);
118     blocksize = sqrt(n);
119     for(rg int i = 1; i <= n; i++) {
120         qread(a[i]);
121         b[i] = a[i];
122         blocknum[i] = (i - 1) / blocksize + 1;
123     }
124     sort(b + 1, b + n + 1);
125     for(rg int i = 1; i <= n; i++) a[i] = lower_bound(b + 1, b + n + 1, a[i]) - b;//离散化,不需要去重
126     int T = M - 5;
127     while(m) {
128         if(m < T) {
129             Solve(m);
130             break;
131         }
132         else Solve(T), m -= T;
133     }//分次求出答案
134     return 0;
135 }
View Code

 

posted @ 2020-10-28 21:08  BreezeEnder  阅读(136)  评论(2编辑  收藏  举报