#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int solve();
int main() {
#ifdef Yinku
freopen("Yinku.in","r",stdin);
#endif // Yinku
solve();
}
int n,m;
const int MAXN=100005;
int tot,v[MAXN],l[MAXN],r[MAXN],d[MAXN],f[MAXN];
//以siz为例代表并查集的信息,属于整棵左偏树
int siz[MAXN];
//这其实是一片左偏树-并查集森林,注意不要抄错Merge和_Merge
class Leftist_Tree {
int _Merge(int x,int y) {
//将两棵左偏树合并,返回他们的新根
//当其中一棵是空树,直接返回另一棵
if(!x||!y)
return x+y;
//此处符号决定是最小堆还是最大堆
//这里是小于号就是最大堆,是大于号就是小根堆
//维持x不比y小,相等则把编号小的那个作为根,相等其实也可以灵活处理
if(v[x]>v[y]||(v[x]==v[y]&&x>y)){
swap(x,y);
}
r[x]=_Merge(r[x],y);
//维持并查集的性质
f[r[x]]=x;
if(d[l[x]]<d[r[x]])
swap(l[x],r[x]);
d[x]=d[r[x]]+1;
return x;
}
int Get_root(int x){
//并查集
//查找编号为x的节点所在的左偏树的根的序号,不需要可以删除
int r=x;
while(f[r]!=r)
r=f[r];
//路径压缩,直接指向他们的根
int k;
while(f[x]!=r){
k=f[x];
f[x]=r;
x=k;
}
return r;
}
public:
void Init(int n) {
//使用v[]中的值初始化一片左偏树-并查集森林
tot=n;
for(int i=1; i<=n; i++) {
l[i]=r[i]=d[i]=0;
//建立并查集,连通块的性质由这里维护
f[i]=i;
}
}
int Top(int x){
//返回x所在的左偏树的堆顶
x=Get_root(x);
return v[x];
}
int Pop(int x) {
//将x所在的左偏树的堆顶弹出
x=Get_root(x);
//题目要求把v[x]删除,这里置为特殊值表示删除
v[x]=-1;
//将两个子树合并,相当于删除了堆顶
//把一棵树的堆顶删除
int rt=_Merge(l[x],r[x]);
//在这里把树根的并查集性质导向新的树根rt,并把大家都指向新树根
siz[x]=siz[l[x]]=siz[r[x]]=siz[x]-1;
f[x]=f[l[x]]=f[r[x]]=f[rt]=rt;
return rt;
}
bool Merge(int x,int y){
x=Get_root(x);
y=Get_root(y);
if(x==y){
//原本就是同一棵左偏树里面的
return false;
}
else{
//在这里更新并查集的信息
siz[x]=siz[y]=(siz[x]+siz[y]);
_Merge(x,y);
return true;
}
}
}lt;
int solve() {
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++) {
scanf("%d",&v[i]);
}
lt.Init(n);
for(int i=1;i<=m;i++){
int z,x,y;
scanf("%d%d",&z,&x);
if(z==1){
scanf("%d",&y);
if(v[x]==-1||v[y]==-1)
continue;
lt.Merge(x,y);
}
else{
if(v[x]==-1){
printf("-1
");
}
else{
printf("%d
",lt.Top(x));
lt.Pop(x);
}
}
}
return 0;
}