这是我的第一道 Ynoi,同时也一发卡到了最优解,发篇题解纪念一下 qwq,不开 $O_2$ 也是能过的。
本题需要的知识点:
- 分块,莫队
- $ ext{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$,显然离散化。同时注意,本题离散化时不能去重,即不需要 $ ext{unique}$,直接使用 $ ext{lower} ext{_bound}$ 即可。
随后对每个询问求删除的数,对于这种对多个区间内元素个数的维护我们不难想到莫队,如莫队板子题 小 Z 的袜子 就是这种题型。
老套路,将长度为 $n$ 的序列拆成 $sqrt n$ 块,把所有询问的区间以 $l$ 为第一关键字,$r$ 为第二关键字排序。
此时考虑如何求出上文的 $rep$。对每个询问开一个长度为 $n$ 的 $ ext{bitset}$,每一位是否为 $1$ 即表示这一位是否被删除。再维护另一个 $ ext{bitset}$ $Bst$,每一位是否为 $1$ 表示这一区间是否有这个数。则最后要删除的数就是三个区间的 $Bst$ 取交集。
最后要注意,
- 此题 $1 le n, m le 10^5$,开不下这么大的 $ ext{bitset}$,需要分几次求答案。
- 区间移动位置时,要先加再减。如果先减,则可能会让 $cnt$ 变成负数再变成正数,会导致 $ ext{bitset}$ 无效访问内存,从而导致 RE。
于是这题就结束了,$ ext{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 ", 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 }