题解 P5397【[Ynoi2018] 天降之物】
这是一篇分块题解!
题意
给你一个长为 $n$ 序列的 $a$,支持操作共 $q$ 次:
- 将所有等于 $x$ 的数修改为 $y$,即 $\forall i\in[1,n]\land a_i=x$,执行 $a_i\gets y$。
- 查询序列中 $x$ 与 $y$ 出现位置两两之差的最小值,即求 $\displaystyle\min_{a_i=x}\min_{a_j=y}|i-j|$。
时间 500ms,$1\le n,q,a_i\le 10^5$.
思路
前置知识:P4119 [Ynoi2018] 未来日记 - 第一分块。
第四分块。
大家都知道标算是根号分治,但我不会!
于是我们使用序列分块!
(说实话还是我第一道自己写出来的大分块,虽然难度不大)
先忽略修改操作,考虑查询分为块内与块间两部分贡献。
块间贡献统计只与每个块内第一次与最后一次出现 $x,y$ 的位置有关,所以维护每个数在每一块中第一次和最后一次的出现位置,分别记为 $fir_{i,j},sec_{i,j}$,其中 $i$ 是块,$j$ 是数。可知这里空间是 $O(n\sqrt n)$ 的。
考虑块内贡献直接维护每两个数之间在该块的答案显然会爆空间。在第一分块我们知道,由于块内的数只会有 $O(\sqrt n)$ 种,可以对块内进行离散化,即对每个块 $i$ 中出现过的数 $j$,记录 $inp_{i,j}$,满足 $inp_{i,j}\le \sqrt n$ 且 $\forall j\ne k,inp_{i,j}\ne inp_{i,k}$。同时,记录其逆 $ind_{i,j}$ 使得 $ind_{i,inp_{i,j}}=j$。这时再记录 $ins_{i,j,k}$ 表示 $ind_{i,j}$ 与 $ind_{i,k}$ 在块中的答案。可知这里空间也是 $O(n\sqrt n)$ 的。
查询时块间与块内分开算。块内就是每个有 $x$ 且有 $y$ 的块 $i$ 中,$ins_{i,inp_{i,x},inp_{i,y}}$ 的最小值。块间则扫每个块,记录上个 $x,y$ 的出现位置,用 $fir$ 计算答案,$sec$ 更新即可。
现在加上了修改操作。
首先我们知道要对每个块考虑。
第一分块中,我们也见过这个玩意。具体地,分类讨论:
- 块中无 $x$:直接跳过;
- 块中有 $x$ 无 $y$:令 $ind_{i,inp_{i,x}}=y,inp_{i,y}=inp_{i,x},inp_{i,x}=0$;
- 块中有 $x$ 有 $y$:令每个块的势能为块中不同数的个数,初始时,每个块的势能是 $O(\sqrt n)$ 的,每次进行此类修改都会将势能减一,所以我们只需将每次此类修改的时间控制在 $O(\sqrt n)$ 即可。显然地,令 $fir_{i,y}=\min(fir_{i,x},fir_{i,y}),sec_{i,y}=\max(sec_{i,x},sec_{i,y}),ins_{i,t,y}=\min(ins_{i,t,x},ins_{i,t,x})$ 即可。
至此,修改部分做到了 $O(n\sqrt n)$。于是我们在时空均为 $O(n\sqrt n)$ 的复杂度内完成了此题。
但是,由于常数较大,我不经任何常数处理的代码只能得到 $16$ 分。
于是我们需要优化(重要的我会标粗):
- 对于空间常数的优化 :
我们可以知道,$inp$ 与 $ins$ 的值都是在 $\sqrt n$ 以内的。考虑用 short 存下它们。
但是 $fir$ 与 $sec$ 的值是 $O(n)$ 的,不能用 short 存(我知道可以将其转化至 $O(\sqrt n)$,但其对时间常数不利),于是用 $fir_{i,inp_{i,x}}$ 代替 $fir_{i,x}$,便可将这部分空间优化至 $O(n)$。
其实我们不需要专门开 $vis_{i,x}$ 表示块 $i$ 中是否有 $x$,用 $inp$ 判断即可。
- 选择合适的块长:典中典,此时块长选 $350$ 可得 $26$ 分。最终卡常时我的块长为 $255$。
- 三角形存储 $ins$ :即只存储 $ins_{i,x,y}(x\le y)$,此时便可得到 $38$ 分。
- 适当降低预处理常数:如减少耗时 $O(n)$ 的循环,此时便可得到 $44$ 分。
- $inp$ 数组的顺序 :用 $inp_{x,i}$ 代替 $inp_{i,x}$,访问连续了许多,此时可得 $80$ 分。
- 适当降低重构常数:减少循环即可,此时可得 $86$ 分。
- 重构分段循环 :由于记录了 $ins_{i,x,y}(x\le y)$,我们在重构时将循环按照与 $inp_{x,i}$ 和 $inp_{y,i}$ 的关系划分为三段,而不是一段并实时判断大小关系,此时可得 $96$ 分。
- 对查询的特判:特判 $x=y$ 的情况便可通过点 $28$,得到 $98$ 分,但我也不知道为什么。
- $ins$ 压缩存储方式 :若将后两位压缩需用到大量乘法操作,耗费时间多。考虑压缩前两维,考虑到第二维小于块长,则令 $ins_{p+x,y}$ 表示 $ins_{i,x,y}$,其中 $p$ 为块 $i$ 的左端点减一。
- 预处理常数再削减 :不要在 $O(n)$ 的循环中使用判断,预先对所有有用的部分进行
memset,只需循环取 $\min$。此时可得 $100$ 分。
使用了上述卡常方式后,仅需微调块长即可通过。
提一句,通过离线逐块处理(这里块间询问平凡,也可逐块处理)可将空间复杂度降至 $O(n)$,但是有强制在线。
代码非常好写,以下代码仅供参考:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
namespace IO{//by cyffff
}
const int N=1e5+10,sq=255,nq=N/sq+10;
int n,q,blk,last,a[N],b[N],bl[nq],br[nq],bel[N],tp[nq];
int fir[N],sec[N],ind[N];
short inp[N][nq],ins[N][sq];
inline int site(int x,int y){
if(x>y) swap(x,y);
return x*sq+y;
}
inline void init(int l,int r){
int d=bel[l],p=l-1;
vector<int>vec;
for(int i=l;i<=r;i++)
vec.push_back(a[i]);
sort(vec.begin(),vec.end());
vec.resize(unique(vec.begin(),vec.end())-vec.begin());
tp[d]=vec.size();
for(int i=l;i<=r;i++)
b[i]=lower_bound(vec.begin(),vec.end(),a[i])-vec.begin()+1,
ind[p+b[i]]=a[i],inp[a[i]][d]=b[i];
for(int i=l;i<=r;i++)
sec[p+b[i]]=i;
for(int i=r;i>=l;i--)
fir[p+b[i]]=i;
for(int i=1;i<=tp[d];i++)
memset(ins[p+i],64,tp[d]+1<<1);
for(int i=l;i<=r;i++)
for(int j=i+1;j<=r;j++){
int A=min(b[i],b[j]),B=max(b[i],b[j]);
ins[p+A][B]=min(ins[p+A][B],(short)(j-i));
}
}
inline void rebuild(int l,int r,int x,int y){
int d=bel[l],p=l-1;
int inpx=inp[x][d],inpy=inp[y][d];
fir[p+inpy]=min(fir[p+inpy],fir[p+inpx]),sec[p+inpy]=max(sec[p+inpy],sec[p+inpx]);
fir[p+inpx]=sec[p+inpx]=0,inp[x][d]=0;
if(inpx>inpy){
for(int i=1;i<=inpy;i++)
ins[p+i][inpy]=min(ins[p+i][inpy],ins[p+i][inpx]);
for(int i=inpy+1;i<=inpx;i++)
ins[p+inpy][i]=min(ins[p+inpy][i],ins[p+i][inpx]);
for(int i=inpx+1;i<=tp[d];i++)
ins[p+inpy][i]=min(ins[p+inpy][i],ins[p+inpx][i]);
}else{
for(int i=1;i<=inpx;i++)
ins[p+i][inpy]=min(ins[p+i][inpy],ins[p+i][inpx]);
for(int i=inpx+1;i<=inpy;i++)
ins[p+i][inpy]=min(ins[p+i][inpy],ins[p+inpx][i]);
for(int i=inpy;i<=tp[d];i++)
ins[p+inpy][i]=min(ins[p+inpy][i],ins[p+inpx][i]);
}
}
int main(){
n=read(),q=read();
for(int i=1;i<=n;i++)
a[i]=read();
for(int i=1;i<=n;i+=sq){
blk++;
bl[blk]=i,br[blk]=min(i+sq-1,n);
for(int j=bl[blk];j<=br[blk];j++)
bel[j]=blk;
init(bl[blk],br[blk]);
}
while(q--){
int opt=read(),x=read()^last,y=read()^last;
if(opt==2){
int ans=n+1,lasx=-1e9,lasy=-1e9;
if(x==y){
for(int i=1;i<=blk;i++)
if(inp[x][i]) ans=0;
if(ans==n+1) puts("Ikaros"),last=0;
else printf("%d\n",last=ans);
continue;
}
for(int i=1;i<=blk;i++){
int inpx=0,inpy=0,p=bl[i]-1;
if(inpx=inp[x][i]) ans=min(ans,fir[p+inpx]-lasy);
if(inpy=inp[y][i]) ans=min(ans,fir[p+inpy]-lasx);
if(inpx&&inpy) ans=min(ans,(int)ins[p+min(inpx,inpy)][max(inpx,inpy)]);
if(inpx) lasx=sec[p+inpx];
if(inpy) lasy=sec[p+inpy];
}
if(ans==n+1) puts("Ikaros"),last=0;
else printf("%d\n",last=ans);
}else{
if(x==y) continue;
for(int i=1;i<=blk;i++){
int t=inp[x][i],p=bl[i]-1;
if(!t) continue;
if(!inp[y][i])
ind[p+t]=y,inp[x][i]=0,inp[y][i]=t;
else
rebuild(bl[i],br[i],x,y);
}
}
// last=0;
}
flush();
}
再见 qwq~

浙公网安备 33010602011771号