SD省队集训2019Day8之“有没有空”

有没有空(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\),用这个阈值来讨论。

  1. \(sz[x],sz[y]\leq lim\)直接暴力查询。

  2. \(sz[x]> lim\)可以预处理出\(x\)与其他所有值的最小距离,然后直接\(O(1)\)查询。预处理的复杂度是\(O(\frac {n}{lim} \ast n)\)

于是可以把\(lim\)设为\(\sqrt n\),这显然是最优的。

但如果加入了修改操作,我们不可能每一次修改之后还暴力重新预处理,应该需要一个东西来优化这个预处理。

我们先研究一下修改的操作。

首先,显然\(x,y\)是等价的两个值,可以直接交换。

  1. \(sz[x],sz[y]<=lim\)直接暴力合并两个的位置集合,这样的复杂度是\(O(lim+lim)\)

  2. \(sz[x],sz[y]>=lim\)直接暴力重构,重新预处理出\(y\)到所有其他值的最小距离。一次重构的复杂度是\(O(n)\)的,但最多重构\(O(\frac {n}{lim})\)次,所以这里的复杂度是\(O(n\ast \frac {n}{lim})\)

  3. \(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\)

那么接下来查询就容易许多了。

  1. \(sz[x],sz[y]<=lim\)依然暴力合并,复杂度\(O(lim+lim)\)

  2. \(sz[x],sz[y]>=lim\)合并\(psz[x]\)\(psz[y]\),再加上\(ans\)里的答案就是真实答案。这样的复杂度是\(O(lim+lim)\)

  3. \(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;
}

转载于:https://www.cnblogs.com/water-lift/p/10993782.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值