P5692 [MtOI2019] 手牵手走向明天
题意
将区间内所有 \(x\) 变为 \(y\),查询区间内值 \(x\) 和值 \(y\) 的下标距离的最小值。
\(n,m,V\le 10^5\)(分别为序列长度,操作数和值域)。
题解
是第四分块:P5397 [Ynoi2018] 天降之物的改版。去掉了强制在线,操作和询问从全局加强到了区间,所以不能用根号分治做法了。
考虑对序列分块,记块长为 \(B\),将答案的统计拆为块内和块间,显然块间的答案只会产生于前面某个块里的最后一个 \(x\) 和后面某个块里的第一个 \(y\),或者是把这里的表述中的 \(x,y\) 交换。
那么考虑维护块内的所有答案,首先我们在每个块内离散化,将值域映射到 \(id\) 上,然后记录每个 \(id\) 的 \(fst\) 和 \(lst\),分别为它在块内第一次和最后一次出现的位置。
那么显然答案的计算就是记录当前的 \(lstx\) 和 \(lsty\) 表示 \(x\) 和 \(y\) 最后出现的位置,散块暴力扫答案并更新这两个值,整块就先用块内答案更新 \(ans\),然后用 \(fst_x-lsty\) 和 \(fst_y-lstx\) 更新 \(ans\)(先要判断块内是否存在 \(x,y\)),然后用 \(lst_x\) 和 \(lst_y\) 更新 \(lstx\) 和 \(lsty\)。
再来考虑修改,讨论一下:
-
散块:暴力扫,修改,然后 \(O(B)\) 暴力重构此块中 \(x,y\) 的信息。此部分总复杂度为 \(O(mB)\)。
-
整块:
-
块内不存在 \(x\):直接忽略,不进行操作;
-
块内存在 \(x\) 但不存在 \(y\):直接修改离散化值,把 \(id_x\) 赋给 \(id_y\),然后将 \(id_x\) 置为 \(0\)。此部分总复杂度为 \(O(\frac{nm}{B})\)。
-
块内 \(x,y\) 都存在:同散块处理,暴力扫,修改,然后 \(O(B)\) 暴力重构此块中 \(x,y\) 的信息。因为在一个块内每进行一次此操作都会使块内不同数值的个数恰好减少一个,所以一个块内最多进行 \(O(B)\) 次此操作,总共最多进行 \(O(n)\) 次。所以此部分总复杂度为 \(O(nB)\)。
-
总时间复杂度为 \(O((n+m)B+\frac{nm}{B})\),空间复杂度为 \(O(nB)\)(瓶颈在于维护整块内两两值的答案),\(B\) 取 \(\sqrt n\) 时即为 \(O((n+m)\sqrt n)\)。
用逐块处理的 trick 可以做到线性空间复杂度。
实现上有一个容易被忽略的细节:
我们在进行散块修改的时候因为可能只修改了块内的一部分 \(x\),所以需要在保留 \(id_x\) 的同时给 \(id_y\) 赋值,这个时候如果一直无脑 ++idx
的话,\(idx\) 最大会达到 \(O(m+B)\) 级别,然后我们维护每个 \(id\) 在块内的信息的时候就有可能造成数组访问越界 RE
,如果直接开大数组,空间复杂度会达到 \(O((m+B)^2)\),就会爆炸。
正确的解决方法是,我们用一个 vector
存下当前“空闲”的 \(id\) 值。我们在块内重构维护某个值的信息的时候,如果这个值在块内没有出现了,那么就把它原先的 \(id\) 值存入 vector
,之后我们就可以用 vector
里面存的“空闲”值,如果 vector
为空我们才 ++idx
。这样可以保证 \(idx\) 始终不会超过块内的不同数值个数,即为 \(O(B)\) 级别。
\(\texttt{Code}\)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define il inline
#define re register
#define getchar()(p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<22,stdin),p1==p2)?EOF:*p1++)
char buf[1<<22],*p1=buf,*p2=buf;
const int N=1e5+10,sq=360,len=350,star=1e8,V=1e5;
int n,m,a[N],L,R,cntB,idx,l,r,x,y;
int fst[sq],lst[sq],ans[sq][sq],id[N];
vector<int>v;
#define pb emplace_back
il int read(){
re int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*f;
}
struct query{
int op,l,r,x,y,ans;
int lstx,lsty;
}q[N];
il void Calc(int &i){
for(re int j=1;j<=idx;j++)ans[i][j]=star;
ans[i][i]=0,lst[i]=0;
for(re int j=L,now=star;j<=R;j++)
if(a[j]==i)now=0,lst[i]=j;
else ans[i][a[j]]=ans[a[j]][i]=min(ans[i][a[j]],++now);
for(re int j=R,now=star;j>=L;j--)
if(a[j]==i)now=0,fst[i]=j;
else ans[i][a[j]]=ans[a[j]][i]=min(ans[i][a[j]],++now);
if(!lst[i])v.pb(i),i=0;
}
il void Build(){
vector<int>().swap(v);
for(re int i=1;i<=V;i++)id[i]=0;
idx=0;
for(re int i=L;i<=R;i++){
if(!id[a[i]])id[a[i]]=++idx;
a[i]=id[a[i]];
}
for(re int i=1;i<=idx;i++)Calc(i);
}
il void Modify(int &x,int &y){
if(!x||x==y)return;
if(l>L||r<R){
if(!y){
if(v.empty())y=++idx;
else y=v.back(),v.pop_back();
}
for(re int i=max(l,L);i<=min(r,R);i++)if(a[i]==x)a[i]=y;
return Calc(x),Calc(y);
}
if(!y)return y=x,x=0,void();
for(re int i=L;i<=R;i++)if(a[i]==x)a[i]=y;
return Calc(x),Calc(y),void();
}
il void Solve(int &res,int &lstx,int &lsty){
x=id[x],y=id[y];
if(!x&&!y)return;
if(l>L||r<R){
for(re int i=max(l,L);i<=min(r,R);i++){
if(a[i]==x&&a[i]==y)return res=0,void();
else if(a[i]==x)res=min(res,i-lsty),lstx=i;
else if(a[i]==y)res=min(res,i-lstx),lsty=i;
}
return;
}
if(x==y)return res=0,void();
if(x)res=min(res,fst[x]-lsty);
if(y)res=min(res,fst[y]-lstx);
if(x)lstx=lst[x];
if(y)lsty=lst[y];
if(x&&y)res=min(res,ans[x][y]);
}
int main(){
n=read(),m=read(),cntB=(n-1)/len+1;
for(re int i=1;i<=n;i++)a[i]=read();
for(re int i=1;i<=m;i++)
q[i].op=read(),q[i].l=read(),q[i].r=read(),
q[i].x=read(),q[i].y=read(),q[i].ans=star,
q[i].lstx=q[i].lsty=-star;
for(re int _=1;_<=cntB;_++){
L=R+1,R=min(_*len,n);
Build();
for(re int i=1;i<=m;i++){
l=q[i].l,r=q[i].r,x=q[i].x,y=q[i].y;
if(l>R||r<L)continue;
if(q[i].op==1)Modify(id[x],id[y]);
else Solve(q[i].ans,q[i].lstx,q[i].lsty);
}
}
for(re int i=1;i<=m;i++)
if(q[i].op==2)cout<<((q[i].ans==star)?-1:q[i].ans)<<'\n';
return 0;
}