左偏堆

bzoj1455罗马游戏

【问题描述】

罗马皇帝很喜欢玩杀人游戏。 他的军队里面有n个人,每个人都是一个独立的团。最近举行了一次平面几何测试,每个人都得到了一个分数。 皇帝很喜欢平面几何,他对那些得分很低的人嗤之以鼻。他决定玩这样一个游戏。 它可以发两种命令: 1. Merger(i, j)。把i所在的团和j所在的团合并成一个团。如果i, j有一个人是死人,那么就忽略该命令。 2. Kill(i)。把i所在的团里面得分最低的人杀死。如果i这个人已经死了,这条命令就忽略。 皇帝希望他每发布一条kill命令,下面的将军就把被杀的人的分数报上来。(如果这条命令被忽略,那么就报0分)

【问题分析】

很明显的左偏堆(树)裸题。我们需要维护一个数据结构能在O(n*logn)内查询最小值以及支持合并操作。那么我们来介绍一下左偏堆。先说一下什么是堆。就是一个类似于二叉搜索树的结构(感觉就是啊),对于每个节点都满足它的俩个子节点小(大)于它本身的权值。但是使用过程中会遇到一个问题,可以构造出特定的序列,使得后加入的点形成一条链,这样的话进行某些操作时,堆就不能有很快的速度了。所以我们用一个类似于维护平衡树的方法来实现左偏堆,每次插入新的元素都插入到节点的右儿子中,然后维护一个深度域,如果发现右儿子深度比左儿子大就swap左右孩子,即可期望深度保证logn层。

#include<bits/stdc++.h>
using namespace std;
const int maxn=10005;
typedef struct node node;
struct node{//值,左右儿编号,老豆(根)编号,死亡标记(1已死),深度标记(用于合并时得到新根后调整左右儿)
    int val;int l;int r;int fa;int die;int dep;
}s[maxn];
int fid(int x){//找X的
    if(s[x].fa!=x)s[x].fa=fid(s[x].fa);
    return s[x].fa;
}
int merge(int x,int y){//传入合并的就是两个根
    if(!x||!y)return x+y;//如果有一个是0表示空(一个叶子根点的左右儿就是0,全局变量没有初始化默认0)
    if(s[x].val>s[y].val)swap(s[x].val,s[y].val);//要把X提为根所以X要保证较小
    s[x].r=merge(s[x].r,y);//合并X的右儿与新根Y
    if(s[s[x].l].dep<s[s[x].r].dep)swap(s[x].l,s[x].r);//左儿比右儿浅就掉换以实现平衡(因为每次都是右子树与新根合并所以左儿肯定浅)
    s[x].dep=s[s[x].r].dep+1;//X的深度更新为右儿的深度+1
    return x;//返回新根
}
int main(){
    int n;cin>>n;//N个点
    for(int i=1;i<=n;i++)cin>>s[i].val;//输入值
    for(int i=1;i<=n;i++)s[i].fa=i;//老豆是自身
    int m;cin>>m;//M个操作
    for(int i=1;i<=m;i++){//遍历
        char ch;cin>>ch;//读入操作符
        if(ch=='M'){//MERGE就是合并
            int x,y;cin>>x>>y;//读入合并的两个点
            if(s[x].die||s[y].die)continue;//如果有一个是死了就跳出
            int f1=fid(x),f2=fid(y);//否则读出两个根
            if(f1==f2)continue;//同根就跳出
            int f3=merge(f1,f2);//否则就合并返回新根
            s[f1].fa=s[f2].fa=f3;//两个结点的老豆都记为这个新根
        }
        else{//删除结点
            int x;cin>>x;//读出删除的结点的编号
            if(s[x].die==1)cout<<'0'<<endl;//已死就输出0
            else{//未死
                int f1=fid(x);//读出其根
                cout<<s[f1].val<<endl;//输出其值
                int f2=merge(s[f1].l,s[f1].r);//合并左右儿子都得到新根
                s[s[f1].l].fa=s[s[f1].r].fa=f2;//新根是原来左右儿子的老豆(新根就是左右儿中的一个)
                s[f1].fa=f2;//已死的F1老豆也要指向新根,因为树下有很多结点的FA还是指向F1的!
            }//上面最后一句是关键!!!
        }
    }
    return 0 ;
}
/*
Sample Input
5
100 90 66 99 10
7
M 1 5
K 1
K 1
M 2 3
M 3 4
K 5
K 4
Sample Output
10
100
0
66
*/

下面是数组版

#include<bits/stdc++.h>
using namespace std;
const int maxn=10005;
int a[maxn],l[maxn],r[maxn],fa[maxn],die[maxn],dep[maxn];
int fid(int x){
    if(fa[x]!=x)fa[x]=fid(fa[x]);
    return fa[x];
}
int merge(int x,int y){//传入合并的就是两个根
    if(!x||!y)return x+y;
    if(a[x]>a[y])swap(x,y);
    r[x]=merge(r[x],y);
    if(dep[l[x]]<dep[r[x]])swap(l[x],r[x]);
    dep[x]=dep[r[x]]+1;
    return x;
}
int main(){
    int n;cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)fa[i]=i;
    int m;cin>>m;
    for(int i=1;i<=m;i++){
        char ch;cin>>ch;
        if(ch=='M'){
            int x,y;cin>>x>>y;
            if(die[x]||die[y])continue;
            int f1=fid(x),f2=fid(y);
            if(f1==f2)continue;
            int f3=merge(f1,f2);
            fa[f1]=fa[f2]=f3;
        }
        else{
            int x;cin>>x;
            if(die[x]==1)cout<<'0'<<endl;
            else{
                int f1=fid(x);
                die[f1]=1;
                cout<<a[f1]<<endl;
                int f2=merge(l[f1],r[f1]);
                fa[f2]=f2;
                fa[f1]=f2//this is the key point ;
            }
        }
    }
    return 0 ;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值