有没有空(busy)([Ynoi2018]天降之物)
给你一个长为 n 的序列 a
你需要实现 m 个操作,操作有两种:
1.把序列中所有值为 x 的数的值变成 y
2.找出一个位置 i 满足 ai==x,找出一个位置 j 满足 aj==y,使得|i-j|最小,并输出|i-j|
部分分:二分
考虑把整个区间分成两部分,那么这两个数要么都在左边,要么都在右边,要么一左一右。前两种情况可以递归解决,同时记录当前区间中最左端和最右段的x和y,然后直接算。
#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[100001];
int fir[100001],to[100001],when[100001],nxt[100001],cnt;
inline void solve(int l,int r,int x,int y,int&ans,int&lx,int&ly,int&rx,int&ry);
inline int calc(int num){
register int tim=0;
while(fir[num]){
register int p=fir[num];
while(when[p]<tim&&nxt[p])p=nxt[p];
if(when[p]<tim)break;
if(to[p]==num)break;
num=to[p];
tim=when[p];
}
return num;
}
int main(){
freopen("busy.in","r",stdin);
freopen("busy.out","w",stdout);
scanf("%d%d",&n,&m);
for(register int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
register int la=0;
for(register int mm=1;mm<=m;mm++){
register int opt,x,y;
scanf("%d%d%d",&opt,&x,&y);
x^=la;
y^=la;
if(opt==1){
to[++cnt]=y;
when[cnt]=mm;
int p=fir[x];
if(!p)fir[x]=cnt;
else{
while(nxt[p])p=nxt[p];
nxt[p]=cnt;
}
}else if(opt==2){
register int ans,lx,ly,rx,ry;
solve(1,n,x,y,ans,lx,ly,rx,ry);
if(ans==-1)printf("Chtholly\n"),la=0;
else printf("%d\n",ans),la=ans;
}
}
}
inline void solve(int l,int r,int x,int y,int&ans,int&lx,int&ly,int&rx,int&ry){
if(l==r){
ans=-1;
if(calc(a[l])==x)lx=rx=l;
else lx=rx=-1;
if(calc(a[r])==y)ly=ry=r;
else ly=ry=-1;
if(x==y&&x==calc(a[l])){
ans=0;
lx=rx=l;
ly=ry=r;
}
return;
}
register int mid=(l+r)/2;
register int lans,llx,lly,lrx,lry;
register int rans,rlx,rly,rrx,rry;
solve(l,mid,x,y,lans,llx,lly,lrx,lry);
solve(mid+1,r,x,y,rans,rlx,rly,rrx,rry);
ans=INT_MAX;
if(lans!=-1&&lans<ans)ans=lans;
if(rans!=-1&&rans<ans)ans=rans;
if(llx==-1)lx=rlx;else lx=llx;
if(lly==-1)ly=rly;else ly=lly;
if(rrx==-1)rx=lrx;else rx=rrx;
if(rry==-1)ry=lry;else ry=rry;
if(lrx!=-1&&rly!=-1)ans=min(ans,rly-lrx);
if(lry!=-1&&rlx!=-1)ans=min(ans,rlx-lry);
if(ans==INT_MAX)ans=-1;
}
做法
根号分治。
首先看这个查询操作。如果我们将所有值的位置按从小到大的顺序记录下来,那么直接暴力查询就是\(sz[x]+sz[y]\),其中\(sz[x]\)表示\(x\)这个值出现的次数。考虑如何优化这个大暴力。于是就可以用根号分治的想法啦。我们设一个阈值为\(lim\),用这个阈值来讨论。
\(sz[x],sz[y]\leq lim\)直接暴力查询。
\(sz[x]> lim\)可以预处理出\(x\)与其他所有值的最小距离,然后直接\(O(1)\)查询。预处理的复杂度是\(O(\frac {n}{lim} \ast n)\)
于是可以把\(lim\)设为\(\sqrt n\),这显然是最优的。
但如果加入了修改操作,我们不可能每一次修改之后还暴力重新预处理,应该需要一个东西来优化这个预处理。
我们先研究一下修改的操作。
首先,显然\(x,y\)是等价的两个值,可以直接交换。
\(sz[x],sz[y]<=lim\)直接暴力合并两个的位置集合,这样的复杂度是\(O(lim+lim)\)。
\(sz[x],sz[y]>=lim\)直接暴力重构,重新预处理出\(y\)到所有其他值的最小距离。一次重构的复杂度是\(O(n)\)的,但最多重构\(O(\frac {n}{lim})\)次,所以这里的复杂度是\(O(n\ast \frac {n}{lim})\)。
\(sz[x]>=lim,sz[y]<=lim\)这里就不好处理了,直接重构也不对,暴力合并也不对。我们发现把\(y\)合并进\(x\)中的均摊位置数是\(O(n)\)的。于是我们可以对于每一个\(sz>=lim\)的数维护一个新的集合\(psz\),再维护一个\(ans[A][B]\)数组,表示所有\(sz>=lim\)的数\(A\)到所有其他数\(B\)去除在\(psz\)里的最小距离。每次将\(y\)合并入\(x\)的时候,我们直接让它合并入\(psz[x]\)中,然后用\(ans[A][y]\)去更新\(ans[A][x]\)。这样的复杂度就变得很好看了。这样的复杂度是\(O(lim+lim)\)的。如果合并后\(psz\)的大小超过\(lim\),则暴力重新处理\(x\)到其他所有数的最小距离。这样的重构式\(O(n)\)的,但最多进行\(O(\frac {n}{lim})\)次,所以这样的复杂度是\(O(n\ast \frac {n}{lim})\)。这样同时也保证pszpszpsz的大小始终小于\(lim\)。
那么接下来查询就容易许多了。
\(sz[x],sz[y]<=lim\)依然暴力合并,复杂度\(O(lim+lim)\)。
\(sz[x],sz[y]>=lim\)合并\(psz[x]\)与\(psz[y]\),再加上\(ans\)里的答案就是真实答案。这样的复杂度是\(O(lim+lim)\)。
\(sz[x]>=lim,sz[y]<=lim\)与上述一样,没区别。
所以总的复杂度就是\(\sum_{i=1}^n (lim+\frac {n}{lim})\),取\(lim=\sqrt(n)\)时最优。
于是就做完了。
代码
#include<bits/stdc++.h>
using namespace std;
#define res register int
#define LL long long
#define inf 0x3f3f3f3f
#define eps 1e-10
#define RG register
inline int read() {//快读
res s=0,ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
return s;
}
inline LL Read() {//快读
RG LL s=0;
res ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
return s;
}
inline void swap(res &x,res &y) {//交换两个变量的值,较快
x^=y^=x^=y;
}
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());//随机数生成器
typedef vector<int> vec;
const int N=1e5+10;
const int BB=500;
namespace MAIN {
vec v[N];
int n,m,block,st[N],top,bigx=1,ans[N/BB+10][N],id[N],big[N],tim,a[N],sz[N],psz[N],lastans,ys[N];
inline int newnode(){ //创建新节点
return top?st[top--]:++bigx;
}
inline void init(const res &B){//初始化,创建新节点时使用
for(res i=1;i<=n;i++)ans[B][i]=inf;
for(res i=1,now=inf;i<=n;i++)
if(id[i]==tim)now=0;
else ans[B][a[i]]=min(ans[B][a[i]],++now);
for(res i=n,now=inf;i;i--)
if(id[i]==tim)now=0;
else ans[B][a[i]]=min(ans[B][a[i]],++now);
}
inline void bigbuild(const res &val){//大于lim时,重构
big[val]=newnode(),tim++;
for(res i=1;i<=n;i++)if(a[i]==val)id[i]=tim;
init(big[val]),ans[big[val]][val]=0;
}
inline void rebuild(const res &A,const res &B){//重构
tim++;
if(big[A])st[++top]=big[A];
for(res i=1;i<=n;i++)if(a[i]==A||a[i]==B)id[i]=tim,a[i]=B;
psz[B]=0,init(big[B]);
}
inline void merge(const res &A,const res &B){//合并
res i=sz[A],j=sz[B]<block?sz[B]:psz[B],k=i+j;
RG vec tmp=v[B];
v[B].resize((k<<1)+10);
while(i&&j)v[B][k--]=(v[A][i]>tmp[j]?v[A][i--]:tmp[j--]);
while(i)v[B][k--]=v[A][i--];
while(j)v[B][k--]=tmp[j--];
}
inline void modify(res x,res y){
res A=ys[x],B=ys[y];
if(A==B||!sz[A])return; //相等或者不存在,无需操作
if(sz[A]>sz[B])swap(A,B),swap(x,y),ys[y]=0,ys[x]=B;
else ys[x]=0;
if(!A||!B)return;
if(sz[B]<block){ //sz小于lim,暴力合并
if(sz[A]+sz[B]<block){ //直接做
merge(A,B);
for(res i=1;i<=bigx;i++)ans[i][B]=min(ans[i][B],ans[i][A]);
for(res i=1;i<=sz[A];i++)a[v[A][i]]=B;
}
else {
big[B]=newnode(),tim++;
for(res i=1;i<=sz[B];i++)id[v[B][i]]=tim;
for(res i=1;i<=sz[A];i++)id[v[A][i]]=tim,a[v[A][i]]=B;
init(big[B]);
}
}
else
if(sz[A]<block){ //sz一大一小
if(psz[B]+sz[A]<block){ //改为合并psz
merge(A,B);
for(res i=1;i<=bigx;i++)ans[i][B]=min(ans[i][B],ans[i][A]);
for(res i=1;i<=sz[A];i++)a[v[A][i]]=B;
psz[B]+=sz[A];
}
else rebuild(A,B); //重构
}
else rebuild(A,B);
sz[B]+=sz[A],sz[A]=0;
}
inline int merge_ans(const res &A,const res &B){
res i=1,j=1,sa=sz[A],sb=sz[B],ret=inf;
if(sz[A]>=block)sa=psz[A]; //大于lim时改为合并psz
if(sz[B]>=block)sb=psz[B]; //同上
if(!sa||!sb)return inf; //不存在则无解,因为调用时是取min,所以可以return无穷大
while(i<=sa&&j<=sb)ret=min(ret,v[A][i]<v[B][j]?v[B][j]-v[A][i++]:v[A][i]-v[B][j++]);//合并
if(i<=sa)ret=min(ret,abs(v[A][i]-v[B][sb]));//求答案
if(j<=sb)ret=min(ret,abs(v[A][sa]-v[B][j]));//同上
return ret;
}
inline int query(res x,res y){
res A=ys[x],B=ys[y];
if(A==B)return sz[A]?0:-1; //相等特判
if(!sz[A]||!sz[B])return -1; //没有出现,肯定无解
if(sz[A]>sz[B])swap(x,y),swap(A,B); //交换后仍等价
if(sz[B]<block)return merge_ans(A,B); //都小于lim,暴力合并
if(sz[A]<block)return min(ans[big[B]][A],merge_ans(A,B));//合并psz[x]和psz[y],再加上ans[][]
return min(min(ans[big[A]][B],ans[big[B]][A]),merge_ans(A,B));//同上
}
inline void MAIN(){
n=read(),m=read(),block=BB;
for(res i=1;i<=n;i++)sz[a[i]=read()]++,ys[i]=i;
for(res i=1;i<=n;i++)
if(sz[i]>=block)bigbuild(i),v[i].resize(10); //sz[x]>lim,重构
else v[i].resize(sz[i]+10),sz[i]=0;
for(res i=1;i<=n;i++)if(sz[a[i]]<block)v[a[i]][++sz[a[i]]]=i;
while(m--){
res opt=read(),x=read()^lastans,y=read()^lastans;
if(opt==1)modify(x,y);
else {
res ret=query(x,y);
if(ret==-1)puts("Chtholly"),lastans=0;
else printf("%d\n",lastans=ret);
}
}
}
}
int main() {
// srand((unsigned)time(NULL));
// freopen("graph.in","r",stdin);
// freopen("graph.out","w",stdout);
MAIN::MAIN();
return 0;
}