[DS记录]P4119 [Ynoi2018]未来日记

· · 个人记录

大 分 块 开 始 了 。

题意 : 最初分块。

区间替换,区间 kth

------------ 首先来考虑静态的区间 $kth$ 怎么分块。 - 经典的值域分块 : 回忆经典的权值线段树上二分,类似地有权值块状树组上的 $kth$。 记录 $c[x]$ 表示 $x$ 有几个,然后对该数组造一棵三层树 ( $n-\sqrt{n}-1$ ) ,在上面爬的复杂度就是$O(\sqrt{n})$。 接下来我们就是要得知一个区间的权值块状树组。 类似主席树的做法,此处可以差分。 给序列分块。我们可以维护**前缀块**的值域分块,这样差分就能得到**完整块**的值域分块。 然后 $O(\sqrt{n})$ 将散块的信息加入即可。 注意,值域分块的外层块只有 $O(\sqrt{n})$ 的信息,所以写法可以比较暴力。 好现在我们会 $O(n\sqrt{n})$ 的静态区间 $kth$ 了,接着来观察这个 $x\rightarrow y$ 的修改。 显然,其只会更新权值块状树组前缀和中的 $O(\sqrt{n})$ 个位置,可以直接暴力。 对于散块,可以暴力修改 $a$。 ( 由于有值域整块前缀和,容易快速判定某个块内是否有 $x$ ) 对于整块,若其中无 $x$ ,可以忽略。若无 $y$ ,可以直接将所有的 $x$ 修改成 $y$。 若 $x,y$ 均有,则直接暴力修改。 - 分析暴力重构的次数。 若所有的操作都只涉及整块,某个块内经过 $O(\sqrt{n})$ 次有效合并必然变为纯色。 一次合并的代价,块数也均是 $O(\sqrt{n})$ ,这部分总复杂度就是 $O(\sqrt{n}^3)=O(n\sqrt{n})$。 接下来考虑散块重构造成的影响,其可以把块中的一部分 $x$ 变为 $y$。 不妨直接视为加入一种新的颜色,则最多导致一次新的合并。 由于每次只有两个散块重构,最多导致额外的 $O(m)$ 次合并,复杂度仍然是 $O\big(n\sqrt{n}\big)$。 考虑如何处理 “$x$ 改名为 $y$” 这一操作,可以搞个数组维护映射。 具体地,为了减小常数,我们维护下列三个值 : `r1[t][i]` 表示块 $t$ 的第 $i$ 个编号对应的值。 `r2[i]` 表示整个序列的第 $i$ 个位置对应的块内编号。 `tp[t][x]` 表示值 $x$ 在块 $t$ 中的编号。可以用 `short` 存储。 具体修改见代码。 时空复杂度均为 $O(n\sqrt{n})$。 将值域分块数组的值维度放在前面,这能显著提升空间局限性。 将值域分块的第二层分叉数目置为为 $256$ ,这样能利用位运算减小常数。 然后调一个合适的块大小,我选的是 $\sqrt{2n/3}$。 ```cpp #include<algorithm> #include<cstring> #include<cstdio> #include<cmath> #define MaxN 105000 using namespace std; const int lim1=400,lim2=100000,bas1=255,bas2=130816; int BS,r1[405][305],a[MaxN],r2[MaxN];short tp[405][MaxN]; int las[MaxN]; void getre(int t) { int tl=t*BS; for (int i=0;i<BS;i++)las[a[i+tl]]=-1; for (int i=0;i<BS;i++){ if (las[a[i+tl]]==-1) tp[t][r1[t][i]=a[i+tl]]=las[a[i+tl]]=i; r2[i+tl]=las[a[i+tl]]; } } int c1[405][405],c2[MaxN][405]; inline void add(int t,int x,int c) {c1[x>>8][t]+=c;c2[x][t]+=c;} int Bn; void gmap(int t) { int tl=t*BS,tr=tl+BS; for (int i=tl;i<tr;i++) a[i]=r1[t][r2[i]]; } int st[405]; void chg(int l,int r,int x,int y) { if (x==y)return ; int bl=l/BS,br=r/BS; if (bl==br){ int c=0; gmap(bl); for (int i=l;i<=r;i++) if (a[i]==x){a[i]=y;c++;} getre(bl); for (int i=bl;i<=Bn;i++) {add(i,x,-c);add(i,y,c);} return ; } gmap(bl);gmap(br); memset(st,0,sizeof(st)); for (int i=l;i<bl*BS+BS;++i) if (a[i]==x){a[i]=y;st[bl]++;} for (int i=br*BS;i<=r;++i) if (a[i]==x){a[i]=y;st[br]++;} getre(bl);getre(br); for (int i=bl+1;i<br;i++){ if (!(st[i]=c2[x][i]-c2[x][i-1]))continue; if (c2[y][i]-c2[y][i-1]==0){ int u=tp[i][x]; tp[i][r1[i][u]=y]=u; }else { gmap(i); int tl=i*BS,tr=tl+BS; for (int j=tl;j<tr;++j) if (a[j]==x)a[j]=y; getre(i); } } for (int i=bl,buf=0;i<=Bn;i++){ buf+=st[i]; add(i,x,-buf);add(i,y,buf); } } int qry(int l,int r,int k) { int bl=l/BS,br=r/BS; if (bl==br){ int tot=0;gmap(bl); for (int i=l;i<=r;i++)st[++tot]=a[i]; nth_element(st+1,st+k,st+tot+1); return st[k]; } gmap(bl);gmap(br); for (int i=0;i<=lim1;++i) st[i]=c1[i][br-1]-c1[i][bl]; for (int i=l;i<bl*BS+BS;++i)++st[a[i]>>8]; for (int i=br*BS;i<=r;++i)++st[a[i]>>8]; int ans; for (int i=0;i<=lim1;++i) if (k>st[i])k-=st[i]; else {ans=i<<8;break;} for (int i=0;i<=bas1;++i) st[i]=c2[ans|i][br-1]-c2[ans|i][bl]; for (int i=l;i<bl*BS+BS;++i) if ((a[i]&bas2)==ans)++st[a[i]&bas1]; for (int i=br*BS;i<=r;++i) if ((a[i]&bas2)==ans)++st[a[i]&bas1]; for (int i=0;i<=bas1;++i){ if (k>st[i])k-=st[i]; else return ans|i; } } int n,m; int main() { scanf("%d%d",&n,&m); BS=sqrt(n*2/3)+1;Bn=(n-1)/BS; for (int i=0;i<n;i++){ scanf("%d",&a[i]); add(i/BS,a[i],1); } getre(0); for (int i=1;i<=Bn;i++)getre(i); for (int j=0;j<=lim1;j++) for (int i=1;i<=Bn;i++) c1[j][i]+=c1[j][i-1]; for (int j=0;j<=lim2;j++) for (int i=1;i<=Bn;i++) c2[j][i]+=c2[j][i-1]; for (int i=0,op,l,r,x,y;i<m;++i){ scanf("%d%d%d%d",&op,&l,&r,&x); l--;r--; if (op==1){ scanf("%d",&y); chg(l,r,x,y); }else printf("%d\n",qry(l,r,x)); }return 0; } ``` [评测记录](https://www.luogu.com.cn/record/46719225) (常数较大)