Soratosorato

P7906 [Ynoi2005] rpxleqxq 题解

Sorato·2024-11-21 19:27·24 次阅读

P7906 [Ynoi2005] rpxleqxq 题解

P7906 [Ynoi2005] rpxleqxq 题解

题目大意#

给定一个长度为 n 的序列 A,和一个常数 k

m 次询问,每次给定一个区间 [l,r],询问有多少二元组 (i,j),满足:

  • 1i<jn
  • (AiAj)k

Solve#

前置知识:莫队二次离线。

对于普通莫队,端点移动时,例如右端点从 r1 移动到 r,对答案的贡献为 [l,r) 内和 r 的异或和小于等于 k 的元素 Ai 的个数。对于这个的维护,一种想法是,枚举 ArAi 是在哪一位上和 k 决出来谁小,这样就确定了合法 Ai 二进制表示下的一个前缀,剩下的位随便填。可以使用 01 trie 维护前缀出现次数,做到插入和查询复杂度都是 O(log2V)

然后考虑二离,维护 f(i)i 左边和它异或和小于等于 k 的元素个数。可以沿用 01 trie,复杂度是 O(nVlog2V+nm)

理一下思路。二离之后,我们有 n 次插入和 nm 次查询。使用 01 trie 的话,可以做到插入和查询复杂度都是 O(log2V)。所以我们考虑平衡一下插入和查询的复杂度,使得查询做到 O(1)

可以在插入 x 时还是枚举决胜位 i,那么对于一个能对其产生贡献的 y,它的 iT 位都是确定的,其中 T=log2V。而 0i1 位可以任意填,即从 y[0,2i11],是一个区间加操作。也就是说,我们需要在值域上维护一个数据结构,支持区间加和单点查,要求单点查复杂度是 O(1)。分块维护即可。这样的话,若块长 B=V,我们就得到了一个 O(nTV+nm) 的算法。理论约 2×109

写完之后手搓极限数据,本地只跑了 1 秒。交上去发现获得了谷的最优解。

复杂度还是不够优秀,理论不是很对。调整一下块长。考虑我插入一个数时,在值域上修改的若干区间都是不交的,所以对于整块的修改复杂度最坏就是 VB 的,而对于散块,修改复杂度是 TB;单次总复杂度是 VB+TB,当 B=VT 时取得最小值,为 TV

这样,算法整体复杂度就来到了 O(nVlog2V+nm)。又交了一发,快了一点。

Code#

Copy
const int N=2e5+10,B=200,T=105,V=2e5; int w[N],sum[N],p[N],l[N],r[N]; inline void modify(const int &x,const int &y)//给 x ~ y 区间加一 { if(x>y) return; int u=p[x],v=p[y]; if(u==v) { for(int i=x;i<=y;i=-~i) w[i]=-~w[i]; return; } for(int i=x;i<=r[u];i=-~i) w[i]=-~w[i]; for(int i=-~u;i<v;i=-~i) sum[i]=-~sum[i]; for(int i=l[v];i<=y;i=-~i) w[i]=-~w[i]; } inline void get_block() { int R=V/T+(V%T!=0); for(int i=1;i<=R;i=-~i) { l[i]=-~r[i-1];r[i]=i==R?V:r[i-1]+T; for(int j=l[i];j<=r[i];j=-~j) p[j]=i; } } inline void insert(const int &x) { for(int i=17,p=0,a,b,now=0;~i;i=~-i) { a=x>>i&1;b=k>>i&1; if(b) modify(now|(a<<i),min(now|(a<<i)|(1<<i)-1,V)); now|=(a^b)<<i; } if((x^k)<=V) w[x^k]=-~w[x^k]; } inline int query(const int &x){return w[x]+sum[p[x]];}

剩下的就是莫二离板子。

posted @   Sorato  阅读(24)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示
目录