CCF NOI Online 2021 提高组 T3 岛屿探险(CDQ 分治,Trie 树)

2022-10-20,,,,

题面

凇睦是一个喜欢探险的女孩子,这天她到一片海域上来探险了。

在这片海域上一共有 n 座岛屿排成一排,标号为 1, 2, 3, . . . , n。每座岛屿有两个权值,分别为劳累度 ai 和有趣度 bi

对于一座劳累度为 a,有趣度为 b 的小岛,如果这个小岛满足 (a ⊕ c) ≤ min(b, d),凇睦到这座岛探险就会感到开心,其中 c 表示凇睦到岛上去之前就有的劳累度(称作初始劳累度),同理 d 代表凇睦的初始有趣度。⊕ 表示二进制异或(即二进制表示下不进位的加法)。

为了玩的更尽兴,凇睦会向你询问 q 次,每次给出一个区间 [li, ri] 和两个数 ci, di,你需要告诉凇睦若她的初始劳累度为 ci,初始有趣度为 di,则有多少个标号在 [li, ri] 这个区间内的岛屿能使凇睦探险时感到开心。

【输入格式】

从文件 island.in 中读入数据。
第一行两个正整数 n, q 分别表示岛屿的数量和询问的数量。
接下来 n 行,每行两个整数 ai, bi 分别表示第 i 座岛屿的劳累度和有趣度。
接下来 q 行,每行四个正整数 li, ri, ci, di 分别表示区间左端点,区间右端点,初始劳累度与初始有趣度。

【输出格式】

输出到文件 island.out 中。
输出一共 q 行,每行一个整数对应一个询问的答案。

题解

我们发现询问可以离线,那就先离线下来。

我们发现 [li, ri] 可以拆成 ri 的贡献减去 li-1 的贡献,那就把它拆开。

然后就可以把所有原序列上的点和所有询问的两个点先按照 bi 或 di 从小到大排序,放在 CDQ 分治中,在分治中按照位置下标排序,这样就消除了 bi 和 di ,以及 [li, ri] 的限制了。接下来才是重头戏,维护 (a ⊕ c) ≤ min(b, d) 这个条件。

我们建两棵 Trie 树,一棵维护 (a ⊕ c) ≤ b ,另一棵维护 (a ⊕ c) ≤ d。


对于第一棵树,其实并没有普通的 Trie 树插入操作那么简单。已知的是 a 和 b,我们可以令 Trie 树上的点表示加进去的 a 对该点范围内的 c 的贡献,这样,我们遍历到 a 的某一位时,假设到了 Trie 树上的结点 p,作如下讨论:

a 的这一位是 1
如果 a 这一位变成 0 ,后面的位置全为 1,a 的值仍然 ≤ b,说明 p 的 “1” 子树内的 c 肯定都符合条件了(不管 c 后面的是什么,只要这一位是 1,a ⊕ c 就能 ≤ b),那么我们就把 p 的 “1” 儿子的权值 ++,表示这个点里所有 c 的答案都+1。然后把 p 赋值为 “0” 儿子继续跑下一位,因为 “0” 儿子不一定全是合法的 c 。
如果 a 这一位变成 0 ,后面的位置全为 1,a 的值 > b,说明 p 的 “0” 子树内的 c 肯定都符合条件(不管 c 后面的是什么,只要这一位是 0,a ⊕ c 就不可能 ≤ b),那么我们就把 p 赋值为 “1” 儿子继续跑下一位,因为 “1” 儿子有可能有合法 c。此时别忘了把 a 的这一位变为 0,表示异或了 1.
a 的这一位是 0,类似的:
如果 a 这一位保持 0 ,后面的位置全为 1,a 的值 ≤ b,说明 p 的 “0” 子树内的 c 肯定都符合条件了(不管 c 后面的是什么,只要这一位是 0,a ⊕ c 就能 ≤ b),那么我们就把 p 的 “0” 儿子的权值 ++ 。然后把 p 赋值为 “1” 儿子继续跑下一位,因为 “1” 儿子里不一定全是合法的 c 。此时别忘了把 a 的这一位变为 1,表示异或了 1.
如果 a 这一位保持 0 ,后面的位置全为 1,a 的值 > b,说明 p 的 “1” 子树内的 c 肯定都不符合条件(不管 c 后面的是什么,只要这一位是 1,a ⊕ c 就不可能 ≤ b),那么我们就把 p 赋值为 “0” 儿子继续跑下一位,因为 “0” 儿子有可能有合法 c。
最后到了一个末结点 p,如果此时的 a ≤ b,那我们还得把 p 的权值 ++,因为之前没统计到。

询问 c 的时候就直接像跑普通 Trie 一样跑一遍,然后统计路径上的权值和就完了。


对于第二棵 Trie 树,由于每个点的 b 没有用了,我们只知道 a ,那么不妨就像普通 Trie 树一样插入 a ,然后统计子树元素个数和。

询问的时候,d 就有用了,此时已知 c, d ,我们在 Trie 树上找合法的 a ,一样是大讨论,别忘了最后到末结点要判断加不加权值。参考第一棵 Tire 树的插入操作。


讲完了!CDQ 分治里我们对于 b ≤ d 和 b > d 的两种情况分别在 Trie1 和 Trie2 上计算就行了。时间复杂度 O(n log2n) 。

CODE

场上差点肝出来的 CDQ 代码(Trie 是调对了的):

#include<set>
#include<map>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 200005
#define MAXC 26
#define DB double
#define LL long long
#define ENDL putchar('\n')
#define lowbit(x) (-(x) & (x))
LL read() {
LL f = 1,x = 0;char s = getchar();
while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
return f * x;
}
int n,m,i,j,s,o,k;
struct tr{
int s[2];
int nm;
tr(){s[0]=s[1]=nm=0;}
};
int CNT1,CNT2;
tr tri1[MAXN*48],tri2[MAXN*32];
int newn1() {tri1[++ CNT1] = tr();return CNT1;}
int newn2() {tri2[++ CNT2] = tr();return CNT2;}
int addtr1(int x,int a,int b) {
if(!x) x = newn1();
int p = x;
for(int i = 23;i >= 0;i --) {
if(!tri1[p].s[0]) tri1[p].s[0] = newn1();
if(!tri1[p].s[1]) tri1[p].s[1] = newn1();
int pr = a - (a & ((1<<(i+1))-1));
int d = (a & (1<<i)) ? 1:0;
if(pr+(1<<i)-1 <= b) {tri1[tri1[p].s[d]].nm ++;p = tri1[p].s[d^1];a ^= ((d^1)*(1<<i));}
else p = tri1[p].s[d],a ^= (d*(1<<i));
}
if(a <= b) tri1[p].nm ++;
return x;
}
int addtr2(int x,int a) {
if(!x) x = newn2();
int p = x;
for(int i = 23;i >= 0;i --) {
int d = (a & (1<<i)) ? 1:0;
if(!tri2[p].s[d]) tri2[p].s[d] = newn2();
p = tri2[p].s[d]; tri2[p].nm ++;
}
return x;
}
int findtr1(int x,int a) {
if(!x) return 0;
int p = x,as = 0;
for(int i = 23;i >= 0;i --) {
int d = ((a & (1<<i)) ? 1:0);
if(!tri1[p].s[d]) return as;
p = tri1[p].s[d]; as += tri1[p].nm;
}return as;
}
int findtr2(int x,int a,int b) {
if(!x) return 0;
int p = x,as = 0;
for(int i = 23;i >= 0;i --) {
if(!p) return as;
int pr = a - (a & ((1<<(i+1))-1));
int d = (a & (1<<i)) ? 1:0;
if(pr+(1<<i)-1 <= b) {as += tri2[tri2[p].s[d]].nm;p = tri2[p].s[d^1];a ^= ((d^1)*(1<<i));}
else p = tri2[p].s[d],a ^= (d*(1<<i));
}
if(a <= b) as += tri2[p].nm;
return as;
}
int ai[MAXN],bi[MAXN];
int ci[MAXN],di[MAXN];
int as[MAXN];
struct it{
int a,b,id,op,pos,ad;it(){a=b=id=op=pos=ad=0;}
it(int D,int O,int P,int A,int B,int I) {ad=D;op=O;pos=P;a=A;b=B;id=I;}
}p[MAXN<<1];
int cntp;
bool cmpb(it x,it y) {return x.b < y.b;}
bool cmpp(it a,it b) {return (a.pos == b.pos ? (a.op > b.op):(a.pos < b.pos));}
void solve(int l,int r) {
if(l >= r) return ;
int mid = (l + r) >> 1;
solve(l,mid);solve(mid+1,r);
sort(p + l,p + r + 1,cmpp);
CNT1 = CNT2 = 0;
int rt1 = 0,rt2 = 0;
for(int i = l;i <= r;i ++) {
if(p[i].op == 1) {
if(p[i].ad <= mid) rt1 = addtr1(rt1,p[i].a,p[i].b);
else rt2 = addtr2(rt2,p[i].a);
}
else {
int y = p[i].id,opt=1;
if(y < 0) y=-y,opt=-opt;
if(p[i].ad <= mid) as[y] += opt*findtr2(rt2,p[i].a,p[i].b);
else as[y] += opt*findtr1(rt1,p[i].a);
}
}
return ;
}
int main() {
freopen("island.in","r",stdin);
freopen("island.out","w",stdout);
n = read();m = read();
for(int i = 1;i <= n;i ++) {
ai[i] = read();bi[i] = read();
p[++ cntp] = it(0,1,i,ai[i],bi[i],i);
}
for(int i = 1;i <= m;i ++) {
s = read();o = read();
ci[i] = read();di[i] = read();
p[++ cntp] = it(0,0,s-1,ci[i],di[i],-i);
p[++ cntp] = it(0,0,o,ci[i],di[i],i);
}
sort(p + 1,p + 1 + cntp,cmpb);
for(int i = 1;i <= cntp;i ++) p[i].ad = i;
solve(1,cntp);
for(int i = 1;i <= m;i ++) {
printf("%d\n",as[i]);
}
return 0;
}
/*
20 10
215 144
2 110
174 132
214 142
116 108
155 192
236 208
216 214
99 220
236 118
190 81
230 131
10 238
189 198
183 13
45 193
14 234
208 192
126 19
49 38
7 14 251 184
2 18 89 76
11 15 49 196
8 11 83 139
10 15 119 239
9 16 148 120
11 17 225 34
15 16 3 46
14 15 86 227
7 18 252 103
*/

CCF NOI Online 2021 提高组 T3 岛屿探险(CDQ 分治,Trie 树)的相关教程结束。

《CCF NOI Online 2021 提高组 T3 岛屿探险(CDQ 分治,Trie 树).doc》

下载本文的Word格式文档,以方便收藏与打印。