//配对堆:pdd //luogu P3377 【模板】左偏树 //选择使用数组结构,故而不用链表去记录右兄弟,直接记录儿子们 #include<cstdio> #include<cstdlib> #include<algorithm> #include<queue> using namespace std; int n,m; const int N=100003; int d[N]; vector <int> son[N]; //并查集维护 int fa[N]; int find(int x) { return fa[x]==0?x:fa[x]=find(fa[x]); } int op,u,v; int merge(int a,int b)//int类型为了传出新的根的序号,在del的多步合并中有作用 { if(a==b) return a;//如果两个根是一样的,那就是一个堆,不用合并,直接忽略其中任意一个元素就好 if(a>b) swap(a,b);//因为题目对删除哪个堆的序号有要求,所以先固定序号顺序 if(d[a]<=d[b]) return fa[b]=a,son[a].push_back(b), a;//更新父子关系,然后返回 else return fa[a]=b,son[b].push_back(a), b; } bool broken[N];//这是一个为del操作存在的变量 //影响范围:本题中只有在del操作会有影响,且只影响这个堆的根部,每次删除的都是根部,所以不会有一个son集合中单个元素被删除 void del(int x) { if(broken[x]) { printf("-1\n"); return ; } //多步合并 x=find(x); printf("%d\n",d[x]); queue <int> s; int sz=son[x].size() ; for(int i=0;i<sz;i++) { int t=son[x][i];//del操作流程: fa[t]=0; //全部变成一个个独立的堆,然后删除老根,然后合并这些新出现的堆 s.push(t); } son[x].clear() ; while( (sz--)>1 )//当队列中有超过一个元素时,去删除一个元素 { int a=s.front();s.pop(); int b=s.front();s.pop(); s.push( merge(a,b) ); } if( !s.empty() ) fa[x]=s.front();//1就算x被删除了,通过路径压缩,要将那些fa指向x的点,指向新的根!!!防止一些点迷路了 broken[x]=true; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&d[i]); while(m--) { scanf("%d%d",&op,&u); if(op==1) {//题目原文:当堆里有多个最小值时,优先删除原序列的靠前的,否则会影响后续操作1导致WA。 scanf("%d",&v);//说明 :如果每次del没有问题,那么merge操作的点应该是都存在的 if(!broken[u] && !broken[v])//2上面那句话是错的!!!!!题目他就是要我验证两个点有没有被删掉!!!!!!!!!!!! merge( find(u),find(v) );//因为这个merge操作是在每个堆的根部进行的操作,而且在delete操作中传入的都是根,所以这里写find比merge中写快些 } else if(op==2) del(u); } return 0; }