岛屿探险
题目链接:luogu P7470
题目大意
有一些岛屿,每个有劳累值 ai 和有趣度 bi。
然后多次询问,给出 c,d 和一个区间范围,问你这个区间范围内有多少个岛屿可以满足 a⊕c<=min(b,d) 这个式子。
思路
首先,我们直接弄这道题没有思路,那我们看看部分分怎么搞。
然后你会发现有两个部分分很特殊,就是式子右边一定是 b 小,或者是 d 小。
那你先考虑这样个情况怎么做。
d 永远是小的
那我们其实就已经不用管
b
b
b 了。
那现在麻烦的就是异或了。
异或,我们就会想到用 Trie。
那岛屿有用的值只有 a,你每次 c,d 都有用,那我们考虑用 a 来建 Trie。
那每次要怎么拿 c,d 去匹配呢?
那肯定是从高位逐位匹配,然后根据它在 Trie 树上跑。
如果 d 的这一位是 0,那为了要让 d 大于等于,那 a 和 c 的这一位就一定要相等。
如果 d 的这一位是 1,那就可以选相等或者不相等。如果相等,那后面都不用匹配了已经会大了,那就直接统计这样的个数。然后再继续往不相等的地方跑。
(统计个数我们可以直接给插入时路径上的点就标记,然后就用标记的次数得出)
那多组询问怎么搞呢?
想到区间,而且前面说了是统计标记次数,自然想到前缀和,那就是可持续化 Trie 树。
b 永远是最小的
那我们试着用上面的思路来,那这次 d 就是没用的了。
那我们会发现如果要用上面的方法来搞的话,每次查询就要每个岛有两个信息,就不知道怎么搞。
那你再想想上面是怎么搞的,它是因为你每次要搜岛的量只有一个,而我们这里是每次查询的量只有一个。
那你就会有一个奇妙的想法:啊!我能不能把查询变成小岛,把小岛变成查询,然后做呢?
啊别说,还真是这么做的。
然后我们具体讲讲如何实现。
那首先,很明显第一步是要把查询给离线了吧。
然后想上面的一样,我们把只有一个变化的(也就是 c)来建 Trie。
那接着跟着上面,就把 a,b 丢进去查询。
那这里很明显就不是要查询了,而是要另外的操作。
那这个操作就是把它贡献到这个 c 里面去(因为 c 终究还是询问)。
那我们前面是给树打标记,然后去搜碰到多少个标记。那我们这里就是去搜,搜到的位置打标记,然后再重新用 c 跑,看 c 跑的路径有多少个标记。
然后接着来看多次询问。
那也是弄成前缀和的形式,你就查询
1
∼
r
1\sim r
1∼r 和
1
∼
l
−
1
1\sim l-1
1∼l−1 的。
那包括标记,左端点都是一样的,就按右端点排序,然后之前标记一下它是属于要加的还是要减的:
1
∼
r
1\sim r
1∼r 的是加,
1
∼
l
−
1
1\sim l-1
1∼l−1 的是减。(当然也要和标记区分开)
如何合在一起
你会发现单纯的合在一起肯定是不行的。
那你考虑先排序(
b
b
b 为第一关键字),然后就搞
cdq
\text{cdq}
cdq 分治。
(因为这样你每次只用考虑右半边对左半边的贡献)
具体怎么搞呢?
首先取出右半边所有的
a
a
a,左半边的
c
,
d
c,d
c,d,然后就是 d 永远最小的情况。同理,取出右半边所有的
c
c
c,左半边的
a
,
b
a,b
a,b,然后就是 b 永远最小的情况。
然后就可以得出答案了。
代码
#include<cstdio>
#include<algorithm>
using namespace std;
struct node {
int a, pl;
}e[200001], ee[300001];
struct Trie {
int son[2], sum;
}trie[6000001];
int n, q, KK, ans[100001];
int a[100001], b[100001], c[100001], d[100001], ql[100001], qr[100001];
int val[100001], Trie_num, root[100001];
bool cmp(node x, node y) {
if (x.a == y.a) return x.pl < y.pl;
return x.a < y.a;
}
int get_new(int now) {
trie[++Trie_num] = trie[now];
return Trie_num;
}
int insert1(int now, int x) {//可持续化的 Trie 树
int root_ = get_new(now);
int now_ = root_;
trie[now_].sum++;
for (int i = 23; i >= 0; i--) {
int go = (x >> i) & 1;
trie[now_].son[go] = get_new(trie[now].son[go]);
trie[trie[now_].son[go]].sum++;
now_ = trie[now_].son[go];
now = trie[now].son[go];
}
return root_;
}
int query1(int x, int y, int c, int d) {//两个时刻的Trie树搜出的值相减
int re = 0;
for (int i = 23; i >= 0; i--) {
if (!x) break;
int cgo = (c >> i) & 1;
int dgo = (d >> i) & 1;
if (dgo) {
re += trie[trie[x].son[cgo]].sum - trie[trie[y].son[cgo]].sum;
}
x = trie[x].son[cgo ^ dgo];
y = trie[y].son[cgo ^ dgo];
}
re += trie[x].sum - trie[y].sum;
return re;
}
void insert2(int now, int x) {//普通 Trie 树
for (int i = 23; i >= 0; i--) {
int go = (x >> i) & 1;
if (!trie[now].son[go]) trie[now].son[go] = get_new(0);
now = trie[now].son[go];
}
}
void add2(int now, int c, int d) {//打标记
for (int i = 23; i >= 0; i--) {
if (!now) break;
int cgo = (c >> i) & 1;
int dgo = (d >> i) & 1;
if (dgo && trie[now].son[cgo]) trie[trie[now].son[cgo]].sum++;
now = trie[now].son[cgo ^ dgo];
}
if (now) trie[now].sum++;
}
int query2(int now, int x) {//查询遇到多少标记
int re = 0;
for (int i = 23; i >= 0; i--) {
re += trie[now].sum;
int go = (x >> i) & 1;
now = trie[now].son[go];
}
re += trie[now].sum;
return re;
}
void cdq(int l, int r) {//cdq 分治
if (l == r) return ;
int mid = (l + r) >> 1;
cdq(l, mid);
cdq(mid + 1, r);
//b[i] > d[i]
int num = 0;
for (int i = mid + 1; i <= r; i++)
if (e[i].pl <= n) {
val[++num] = e[i].pl;
}
sort(val + 1, val + num + 1);
Trie_num = 0;
for (int i = 1; i <= num; i++) {
root[i] = insert1(root[i - 1], a[val[i]]);
}
for (int i = l; i <= mid; i++)
if (e[i].pl > n) {
int id = e[i].pl - n;
int L = lower_bound(val + 1, val + num + 1, ql[id]) - val;
int R = upper_bound(val + 1, val + num + 1, qr[id]) - val - 1;
if (L > R) continue;
ans[id] += query1(root[R], root[L - 1], c[id], d[id]);
}
//b[i] <= d[i]
Trie_num = 0;
get_new(0);
num = 0;
for (int i = mid + 1; i <= r; i++)
if (e[i].pl > n) {
int id = e[i].pl - n;
insert2(1, c[id]);
ee[++num] = (node){ql[id] - 1, id + n};
ee[++num] = (node){qr[id], id + n + q};
}
for (int i = l; i <= mid; i++)
if (e[i].pl <= n) {
ee[++num] = (node){e[i].pl, e[i].pl};
}
sort(ee + 1, ee + num + 1, cmp);
for (int i = 1; i <= num; i++) {
if (ee[i].pl <= n) {
add2(1, a[ee[i].pl], b[ee[i].pl]);
}
else if (ee[i].pl <= n + q) {
int id = ee[i].pl - n;
ans[id] -= query2(1, c[id]);
}
else {
int id = ee[i].pl - n - q;
ans[id] += query2(1, c[id]);
}
}
}
int main() {
scanf("%d %d", &n, &q);
for (int i = 1; i <= n; i++) {
scanf("%d %d", &a[i], &b[i]);
e[++KK] = (node){b[i], i};
}
for (int i = 1; i <= q; i++) {
scanf("%d %d %d %d", &ql[i], &qr[i], &c[i], &d[i]);
e[++KK] = (node){d[i], n + i};
}
sort(e + 1, e + n + q + 1, cmp);
cdq(1, n + q);
for (int i = 1; i <= q; i++)
printf("%d\n", ans[i]);
return 0;
}