题解 P5397【[Ynoi2018] 天降之物】

$\text{Link}$

这是一篇分块题解!

题意

给你一个长为 $n$ 序列的 $a$,支持操作共 $q$ 次:

  1. 将所有等于 $x$ 的数修改为 $y$,即 $\forall i\in[1,n]\land a_i=x$,执行 $a_i\gets y$。
  2. 查询序列中 $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$ 更新即可。


现在加上了修改操作。

首先我们知道要对每个块考虑。

第一分块中,我们也见过这个玩意。具体地,分类讨论:

  1. 块中无 $x$:直接跳过;
  2. 块中有 $x$ 无 $y$:令 $ind_{i,inp_{i,x}}=y,inp_{i,y}=inp_{i,x},inp_{i,x}=0$;
  3. 块中有 $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$ 分。

于是我们需要优化(重要的我会标粗):

  1. 对于空间常数的优化

我们可以知道,$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$ 判断即可。

  1. 选择合适的块长:典中典,此时块长选 $350$ 可得 $26$ 分。最终卡常时我的块长为 $255$。
  2. 三角形存储 $ins$ :即只存储 $ins_{i,x,y}(x\le y)$,此时便可得到 $38$ 分。
  3. 适当降低预处理常数:如减少耗时 $O(n)$ 的循环,此时便可得到 $44$ 分。
  4. $inp$ 数组的顺序 :用 $inp_{x,i}$ 代替 $inp_{i,x}$,访问连续了许多,此时可得 $80$ 分。
  5. 适当降低重构常数:减少循环即可,此时可得 $86$ 分。
  6. 重构分段循环 :由于记录了 $ins_{i,x,y}(x\le y)$,我们在重构时将循环按照与 $inp_{x,i}$ 和 $inp_{y,i}$ 的关系划分为三段,而不是一段并实时判断大小关系,此时可得 $96$ 分。
  7. 对查询的特判:特判 $x=y$ 的情况便可通过点 $28$,得到 $98$ 分,但我也不知道为什么。
  8. $ins$ 压缩存储方式 :若将后两位压缩需用到大量乘法操作,耗费时间多。考虑压缩前两维,考虑到第二维小于块长,则令 $ins_{p+x,y}$ 表示 $ins_{i,x,y}$,其中 $p$ 为块 $i$ 的左端点减一。
  9. 预处理常数再削减 :不要在 $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~

posted @ 2022-03-09 16:12  ffffyc  阅读(23)  评论(0)    收藏  举报  来源