【模板】【数据结构】【堆】配对堆(合并堆)

//配对堆: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;
}

 

转载于:https://www.cnblogs.com/xwww666666/p/11260578.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值