题解 P5064【[Ynoi2014] 等这场战争结束之后】

$\text{Link}$

题意

给一张 $n$ 个结点的空图,每个结点有点权 $a_i$,支持操作共 $m$ 次:

  1. 连边
  2. 将图恢复到某次操作后的状态
  3. 查询连通块 $k$ 小

数据范围:$n,m\le 10^5$.

思路

这个题比较的简单。

建出操作树,每一条边代表一次操作,一个结点代表一个状态。

连通块 $k$ 小有许多做法,但我们需要支持快速合并、回撤,所以我们选择值域分块。

先把序列离散化成 $[1,n]$ 的排列。考虑合并值域分块:整块可以直接合并,散块却不好处理。

我们思考一下,发现散块的作用是查询连通块内有没有数 $x$,这个并查集也能做到,并且并查集容易撤销。

撤销时整块可以直接减掉,并查集可以使用可撤销并查集。

令块长为 $T=\Theta(\sqrt{\dfrac n{\log n}})$,时间复杂度为 $\Theta(\dfrac{nm}T+mT\log n)=\Theta(m\sqrt{n\log n})$。

但是,空间复杂度为 $\Theta(nT)=\Theta(\dfrac{n\sqrt n}{\sqrt{\log n}})$,无法达到题目要求的空间。

首先,$T<32768$,所以整块可以用 short 类型存。

然后试着调块长,我调到 $T=4200$ 的时候时空都压线过了。

代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
namespace IO{//by cyffff

}
const int N=1e5+10,sq=4200;
int n,m,bl[N],br[N],bel[N];
int a[N],rev[N],pr[N],op[N],ans[N];
short bnt,blo[N][N/sq+10];
int head[N],cnt,nxt[N];
struct Edge{
    int to,nxt;
}s[N];
inline void add(int u,int v){
    cnt++;
    s[cnt].to=v;
    s[cnt].nxt=head[u];
    head[u]=cnt;
}
pair<int,int>b[N];
struct node{
    int x,y;
}as[N];
namespace ds{
    int f[N],siz[N];
    inline void init(int sz){
        for(int i=1;i<=sz;i++)
            f[i]=i,siz[i]=1;
    }
    inline int find(int x){
        for(;x!=f[x];x=f[x]);
        return x;
    }
    inline void swap(int &a,int &b){
        int c;
        if(siz[a]>siz[b]) c=a,a=b,b=c;
    }
    inline void merge(int x,int y){
        f[x]=y,siz[y]+=siz[x];
    }
    inline void split(int x,int y){
        siz[y]-=siz[x],f[x]=x;
    }
}
inline void dfs(int x){
    int fx,fy;
    if(op[x]==1){
        int tx=as[x].x,ty=as[x].y;
        fx=ds::find(tx),fy=ds::find(ty);
        if(fx==fy) goto out;
        ds::swap(fx,fy);
        for(int i=1;i<=bnt;i++)
            blo[fy][i]+=blo[fx][i];
        ds::f[fx]=fy,ds::siz[fy]+=ds::siz[fx];
    }
    if(op[x]==3){
        int tx=as[x].x,k=as[x].y,tmp=-1;
        tx=ds::find(tx);
        for(int p=1;p<=bnt;p++)
            if(blo[tx][p]<k) k-=blo[tx][p];
            else{ tmp=p;break; }
        if(tmp==-1){ ans[x]=-1;goto out; }
        for(int i=bl[tmp];;i++)
            if(ds::find(pr[i])==tx)
                if(k>1) k--;
                else{ ans[x]=rev[i];break; }
    }
    out:;
    for(int i=head[x];i;i=s[i].nxt) dfs(s[i].to);
    if(op[x]==1&&fx!=fy){
        for(int i=1;i<=bnt;i++)
            blo[fy][i]-=blo[fx][i];
        ds::siz[fy]-=ds::siz[fx],ds::f[fx]=fx;
    }
}
int main(){
    n=read(),m=read();
    for(int i=1;i<=n;i++)
        b[i]=make_pair(a[i]=read(),i);
    sort(b+1,b+n+1);
    for(int i=1;i<=n;i++){
        int t=a[i];
        a[i]=lower_bound(b+1,b+n+1,make_pair(a[i],i))-b;
        pr[a[i]]=i,rev[a[i]]=t;
    }
    bnt=0;
    for(int i=1;i<=n;i+=sq)
        bl[++bnt]=i,br[bnt]=min(i+sq-1,n);
    for(int i=1;i<=bnt;i++)
        for(int j=bl[i];j<=br[i];j++) 
            bel[j]=i;
    ds::init(n);
    for(int i=1;i<=n;i++)
        blo[i][bel[a[i]]]=1;
    for(int i=1;i<=m;i++){
        op[i]=read();
        if(op[i]!=2) as[i]={read(),read()},add(i-1,i);
        else add(read(),i);
    }
    dfs(0);
    for(int i=1;i<=m;i++)
        if(op[i]==3) printf("%d\n",ans[i]);
}

再见 qwq~

posted @ 2022-02-08 10:46  ffffyc  阅读(20)  评论(0)    收藏  举报  来源