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

Link

这是一篇分块题解!

题意

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

  1. 将所有等于 x 的数修改为 y,即 i[1,n]ai=x,执行 aiy
  2. 查询序列中 xy 出现位置两两之差的最小值,即求 minai=xminaj=y|ij|

时间 500ms1n,q,ai105.

思路

前置知识:P4119 [Ynoi2018] 未来日记 - 第一分块

第四分块。

大家都知道标算是根号分治,但我不会!

于是我们使用序列分块!

(说实话还是我第一道自己写出来的大分块,虽然难度不大)


先忽略修改操作,考虑查询分为块内与块间两部分贡献。

块间贡献统计只与每个块内第一次与最后一次出现 x,y 的位置有关,所以维护每个数在每一块中第一次和最后一次的出现位置,分别记为 firi,j,seci,j,其中 i 是块,j 是数。可知这里空间是 O(nn) 的。

考虑块内贡献直接维护每两个数之间在该块的答案显然会爆空间。在第一分块我们知道,由于块内的数只会有 O(n) 种,可以对块内进行离散化,即对每个块 i 中出现过的数 j,记录 inpi,j,满足 inpi,jnjk,inpi,jinpi,k。同时,记录其逆 indi,j 使得 indi,inpi,j=j。这时再记录 insi,j,k 表示 indi,jindi,k 在块中的答案。可知这里空间也是 O(nn) 的。

查询时块间与块内分开算。块内就是每个有 x 且有 y 的块 i 中,insi,inpi,x,inpi,y 的最小值。块间则扫每个块,记录上个 x,y 的出现位置,用 fir 计算答案,sec 更新即可。


现在加上了修改操作。

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

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

  1. 块中无 x:直接跳过;
  2. 块中有 xy:令 indi,inpi,x=y,inpi,y=inpi,x,inpi,x=0
  3. 块中有 xy:令每个块的势能为块中不同数的个数,初始时,每个块的势能是 O(n) 的,每次进行此类修改都会将势能减一,所以我们只需将每次此类修改的时间控制在 O(n) 即可。显然地,令 firi,y=min(firi,x,firi,y),seci,y=max(seci,x,seci,y),insi,t,y=min(insi,t,x,insi,t,x) 即可。

至此,修改部分做到了 O(nn)。于是我们在时空均为 O(nn) 的复杂度内完成了此题。


但是,由于常数较大,我不经任何常数处理的代码只能得到 16 分。

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

  1. 对于空间常数的优化

我们可以知道,inpins 的值都是在 n 以内的。考虑用 short 存下它们。

但是 firsec 的值是 O(n) 的,不能用 short 存(我知道可以将其转化至 O(n),但其对时间常数不利),于是用 firi,inpi,x 代替 firi,x,便可将这部分空间优化至 O(n)

其实我们不需要专门开 visi,x 表示块 i 中是否有 x,用 inp 判断即可。

  1. 选择合适的块长:典中典,此时块长选 350 可得 26 分。最终卡常时我的块长为 255
  2. 三角形存储 ins :即只存储 insi,x,y(xy),此时便可得到 38 分。
  3. 适当降低预处理常数:如减少耗时 O(n) 的循环,此时便可得到 44 分。
  4. inp 数组的顺序 :用 inpx,i 代替 inpi,x,访问连续了许多,此时可得 80 分。
  5. 适当降低重构常数:减少循环即可,此时可得 86 分。
  6. 重构分段循环 :由于记录了 insi,x,y(xy),我们在重构时将循环按照与 inpx,iinpy,i 的关系划分为三段,而不是一段并实时判断大小关系,此时可得 96 分。
  7. 对查询的特判:特判 x=y 的情况便可通过点 28,得到 98 分,但我也不知道为什么。
  8. ins 压缩存储方式 :若将后两位压缩需用到大量乘法操作,耗费时间多。考虑压缩前两维,考虑到第二维小于块长,则令 insp+x,y 表示 insi,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 @   ffffyc  阅读(12)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示