【luogu P7470】岛屿探险

岛屿探险

题目链接:luogu P7470

题目大意

有一些岛屿,每个有劳累值 ai 和有趣度 bi。
然后多次询问,给出 c,d 和一个区间范围,问你这个区间范围内有多少个岛屿可以满足 a⊕c<=min(b,d) 这个式子。

思路

首先,我们直接弄这道题没有思路,那我们看看部分分怎么搞。

然后你会发现有两个部分分很特殊,就是式子右边一定是 b 小,或者是 d 小。
那你先考虑这样个情况怎么做。

d 永远是小的

那我们其实就已经不用管 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 跑的路径有多少个标记。

然后接着来看多次询问。
那也是弄成前缀和的形式,你就查询 1r1l1 的。
那包括标记,左端点都是一样的,就按右端点排序,然后之前标记一下它是属于要加的还是要减的: 1r 的是加,1l1 的是减。(当然也要和标记区分开)

如何合在一起

你会发现单纯的合在一起肯定是不行的。

那你考虑先排序(b 为第一关键字),然后就搞 cdq 分治。
(因为这样你每次只用考虑右半边对左半边的贡献)

具体怎么搞呢?
首先取出右半边所有的 a,左半边的 c,d,然后就是 d 永远最小的情况。同理,取出右半边所有的 c,左半边的 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; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/luogu_P7470.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(94)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示