二项堆运用——hdu1512解题报告

题目的大意是一堆数,选择其中的两个,如果他们是不同群体的话,就选出各自最大的一个,然后PK,减半,并合并在一起。

查找是否是两个不同的群体,首先应该想到的就是并查集。

接着就是思考是用什么数据结构,能够合并两个集合,在高级数据结构里面有二项堆和左偏树。

这里是用二项堆来写的,刚开始的时候一直没能把并查集和二项堆很好的运用在一起,看了一篇Pascal的代码,发现别人处理的很巧妙,就用每个二项堆的根来当作这个二项堆所有节点的并查集父亲节点,而且路径压缩也非常巧妙,这里没有路径压缩会超时,思路是把查找一个节点x的父亲节点时所有通过的路径上节点的父亲全部指向二项堆的根节点。

并查集可以各种变换使用,这里实在自己没想到。

struct BinHeap
{
    int sibling,leftChild,parent;
    int key,degree;
    int fa;
} data[N];

void init(int n)//初始化
{
    for(int i = 1 ; i <= n ; i ++)
    {
        data[i].fa = 0;//开始父亲节点都是0
        data[i].degree = 0;
        data[i].sibling = 0;
        data[i].leftChild = 0;
        data[i].parent = 0;
        scanf("%d",&data[i].key);
    }
}

int find(int x)//寻找x所在二项堆的根节点,并查集
{
    int p = x;
    while(data[x].fa != 0) x = data[x].fa;//找到x的父亲,即根节点
    while(p != x)//路径压缩,把查找路径上的所有节点的父亲全部改为根节点,即现在的x
    {
        int temp = data[p].fa;
        data[p].fa = x;
        p = temp;
    }
    return x;
}

int findmax(int x)//查找二项堆中的最大值,最大值肯定在根表中
{
    int max = -1,p;
    while(x != 0)
    {
        if(data[x].key > max)
        {
            max = data[x].key;
            p = x;
        }
        x = data[x].sibling;
    }
    return p;
}

void Link(int x,int y)//合并两个degree相等的两个二项树
{
    data[x].parent = y;
    data[x].sibling = data[y].leftChild;
    data[y].leftChild = x;
    data[y].degree = data[y].degree + 1;
}

int merge(int x,int y)//对根表进行归并排序,按照degree从小到大排序
{
    int p,q;
    if(data[x].degree <= data[y].degree)
    {
        p = x;
        x = data[x].sibling;
    }
    else
    {
        p = y;
        y = data[y].sibling;
    }
    q = p;
    while(x != 0 && y != 0)
    {
        if(data[x].degree <= data[y].degree)
        {
            data[p].sibling = x;
            p = x;
            x = data[x].sibling;
        }
        else
        {
            data[p].sibling = y;
            p = y;
            y = data[y].sibling;
        }
    }
    if(x != 0) data[p].sibling = x;
    if(y != 0) data[p].sibling = y;
    return q;
}

int HeapUnion(int x,int y)//合并两个二项堆
{
    int heap = merge(x,y);
    int p = heap,pre = 0;
    int next = data[p].sibling;
    while(next != 0)
    {
        if(data[p].degree != data[next].degree||
                (data[next].sibling != 0 && data[p].degree == data[data[next].sibling].degree))
        {
            pre = p;
            p = next;
        }
        else if(data[p].key >= data[next].key)
        {
            data[p].sibling = data[next].sibling;
            Link(next,p);
        }
        else
        {
            if(pre == 0) heap = next;
            else data[pre].sibling = next;
            Link(p,next);
            p = next;
        }
        next = data[p].sibling;
    }
    //这里重新对x,y的并查集父亲进行赋值,都是二项堆的最左根节点
    data[x].fa = heap;
    data[y].fa = heap;
    data[heap].fa = 0;
    return heap;
}

void make(int x)//调整二项树,由于二项堆中的每个二项树是最大堆,所以只需要逐层调整即可,找到每层的最大值交换
{
    if(x == 0) return;
    int tp = data[x].leftChild;
    int max = -1;
    int q = 0;
    while(tp != 0)//找到每层的最大值
    {
        if(data[tp].key > max)
        {
            max = data[tp].key;
            q = tp;
        }
        tp = data[tp].sibling;
    }
    if(q != 0 && data[x].key < data[q].key)//交换
    {
        int temp = data[x].key;
        data[x].key = data[q].key;
        data[q].key = temp;
        make(q);
    }
}

void solve(int a,int b)
{
    int x = find(a),y = find(b);//找到a,b属于哪个二项堆
    if(x == y)
    {
        puts("-1");
        return;
    }
    int p = findmax(x);//找到最大值节点
    int q = findmax(y);
    data[p].key /= 2;
    data[q].key /= 2;
    make(p);
    make(q);
    printf("%d\n",data[findmax(HeapUnion(x,y))].key);
}

int main()
{
    int n,m,a,b;
    while(~scanf("%d",&n))
    {
        init(n);
        scanf("%d",&m);
        while(m--)
        {
            scanf("%d %d",&a,&b);
            solve(a,b);
        }
    }
    return 0;
}


你可能感兴趣的:(HDU解题报告)