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

有没有空(busy)([Ynoi2018]天降之物)

给你一个长为 n 的序列 a
你需要实现 m 个操作,操作有两种:
1.把序列中所有值为 x 的数的值变成 y
2.找出一个位置 i 满足 ai==x,找出一个位置 j 满足 aj==y,使得|i-j|最小,并输出|i-j|

部分分:二分

考虑把整个区间分成两部分,那么这两个数要么都在左边,要么都在右边,要么一左一右。前两种情况可以递归解决,同时记录当前区间中最左端和最右段的x和y,然后直接算。

#include
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]

做法

根号分治。

首先看这个查询操作。如果我们将所有值的位置按从小到大的顺序记录下来,那么直接暴力查询就是\(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
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 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]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)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]sz[B])swap(x,y),swap(A,B);         //交换后仍等价
        if(sz[B]=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]]

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

你可能感兴趣的:(SD省队集训2019Day8之“有没有空”)