- 给定一张(n)个点的图,初始没有边。
- 要求支持三种操作:加入一条无向边;回到之前某次操作后的状态;询问一个连通块的第(k)小值。
- (n,qle10^5)
操作树上(dfs)
这类问题的经典操作。
只要建出操作树,就只需要考虑一次操作的加入与撤销即可。
值域分块+并查集
首先这道题必须要离散化,但离散化时我们不应去重,这样就可以保证离散化后的值与原数一一对应(然而好像数据里面并没有重复的数)。
然后把值域分块,对于每个连通块维护每个块的数的个数。
询问答案时先找到答案在哪个块,再枚举这个块中的每个值,找到对应点判断是否与询问点连通(可以使用并查集维护)。
注意这道题卡内存,由于一个值域块内的数不超过(S)个可以开成(short),那么块长(S)可以取到(2500),块个数为(40)。
代码:(O(nsqrt{nlogn}))
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define BS 2500
#define BT 40
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
using namespace std;
int n,Qt,a[N+5],id[N+5];struct Q {int op,x,y;}q[N+5];
int ee,lnk[N+5];struct edge {int to,nxt;}e[N+5];
namespace FastIO
{
#define FS 100000
#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
Tp I void writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('
');}
I void NA() {pc('-'),pc('1'),pc('
');}
}using namespace FastIO;
namespace V//值域分块
{
int sz,bl[N+5],p[N+5];short c[N+1][BT+1];
I void U(CI x,CI y,CI op) {for(RI i=1;i<=bl[n];++i) c[x][i]+=op*c[y][i];}//给x的数组加上/减去y的数组
int dv[N+5],nw[N+5];I void Build()//预处理
{
RI i,x;for(sz=min(BS,n),i=1;i<=n;++i) bl[i]=(i-1)/sz+1;for(i=1;i<=n;++i) dv[i]=a[i];sort(dv+1,dv+n+1);
for(i=1;i<=n;++i) c[i][bl[a[i]=nw[x=lower_bound(dv+1,dv+n+1,a[i])-dv]?++nw[x]:(nw[x]=x)]]=1,p[a[i]]=i;//不去重离散化,并对应到原数
}
}
namespace U//按秩合并可撤销并查集
{
int f[N+5],g[N+5],T,Sx[N+5],Sy[N+5];I int fa(CI x) {return f[x]?fa(f[x]):x;}
I void Init() {for(RI i=1;i<=n;++i) g[i]=1;}//初始赋值
I void Union(RI x,RI y)//合并
{
++T;if((x=fa(x))==(y=fa(y))) return (void)(Sx[T]=-1);//已连通则合并无效
g[x]<g[y]&&(swap(x,y),0),g[Sx[T]=f[y]=x]+=g[Sy[T]=y],V::U(x,y,1);
}
I void Back() {~Sx[T]&&(g[Sx[T]]-=g[Sy[T]],V::U(Sx[T],Sy[T],-1),f[Sy[T]]=0),--T;}//撤销
}
I int Q(RI x,RI y)//询问x所在连通块第y大
{
if(U::g[x=U::fa(x)]<y) return -1;RI i;for(i=1;V::c[x][i]<y;++i) y-=V::c[x][i];//找到在哪个块
for(i=(i-1)*V::sz+1;;++i) if(U::fa(V::p[i])==x&&!--y) return i;//枚举块中值找到对应点判连通
}
int ans[N+5];I void dfs(CI x)//操作树上dfs
{
q[x].op==1&&(U::Union(q[x].x,q[x].y),0),q[x].op==3&&(ans[x]=Q(q[x].x,q[x].y));//处理当前点操作
for(RI i=lnk[x];i;i=e[i].nxt) dfs(e[i].to);q[x].op==1&&(U::Back(),0);//遍历子树,撤销操作
}
int main()
{
RI i,op,x;for(read(n,Qt),i=1;i<=n;++i) read(a[i]);V::Build();
for(i=1;i<=Qt;++i) read(op),op^2?(q[i].op=op,read(q[i].x,q[i].y),add(id[i-1],i),id[i]=i):(read(x),id[i]=id[x]);//id[i]记录第i个操作后对应节点
for(U::Init(),dfs(0),i=1;i<=Qt;++i) q[i].op==3&&(~ans[i]?writeln(V::dv[ans[i]]):NA(),0);return clear(),0;
}