1. 程式人生 > >Monkey King(左偏樹 可並堆)

Monkey King(左偏樹 可並堆)

not code 根節點 平衡 \n order ren itl live

  我們知道如果要我們給一個序列排序,按照某種大小順序關系,我們很容易想到優先隊列,的確很方便,但是優先隊列也有解決不了的問題,當題目要求你把兩個優先隊列合並的時候,這就實現不了了

優先隊列只有插入 刪除 取數的操作,但是卻沒有合並兩個優先隊列的操作。 這也是它的局限所在。

  本次要介紹的左偏樹擁有優先隊列的所有功能,同時它還可以合並操作。 樹的復雜度都比較低,一般log(n)就夠了,左偏樹也是如此,左偏樹如果一個個結點暴力插入復雜度最大為nlog(n)

還有一種仿照二叉樹的算法,這裏不做介紹。 下面仔細說一下什麽是左偏樹:

  H ← Merge(H1,H2):

  Merge( ) 構造並返回一個包含H1和H2所有元素的新堆H。

  左偏樹的定義:左偏樹(Leftist Tree)是一種可並堆的實現。左偏樹是一棵二叉樹,它的節點除了和二叉樹的節點一樣具有左右子樹指針( left, right )外,

還有兩個屬性:鍵值和距離(dist)。鍵值上面已經說過,是用於比較節點的大小。距離則是如下定義的:

節點 i 稱為外節點(external node),當且僅當節點 i 的左子樹或右子樹為空( left(i) = NULL 或 right(i) = NULL );

節點 i 的距離( dist( i ) )是節點 i 到它的後代中,最近的外節點所經過的邊數。特別的,如果節點 i 本身是外節點,則它的距離為 0;

而空節點的距離規定為-1 (dist(NULL) = -1)。在本文中,有時也提到一棵左偏樹的距離,這指的是該樹根節點的距離。

左偏樹滿足下面兩條基本性質:

[性質 1] 節點的鍵值小於或等於它的左右子節點的鍵值。

即 key(i)≤key(parent(i)) 這條性質又叫堆性質。符合該性質的樹是堆有序

的(Heap-Ordered)。有了性質 1,我們可以知道左偏樹的根節點是整棵樹的最小 節點(也可以使得它最大),於是我們可以在 O(1) 的時間內完成取最小節點操作。


[性質 2] 節點的左子節點的距離不小於右子節點的距離。
即 dist(left(i))≥dist(right(i)) 這條性質稱為左偏性質。性質 2 是為了使我們可以以更小的代價在優先隊列的其它兩個基本操作(插入節點、刪除最小節點)


進行後維持堆性質。在後面我們就會看到它的作用。

這兩條性質是對每一個節點而言的,因此可以簡單地從中得出,左偏樹的左右子樹都是左偏樹。

由這兩條性質,我們可以得出左偏樹的定義:左偏樹是具有左偏性質的堆有序二叉樹。

下圖是一棵左偏樹:

技術分享圖片

2.3 左偏樹的性質
在前面一節中,本文已經介紹了左偏樹的兩個基本性質,下面本文將介紹左偏樹的另外兩個性質。

我們知道,一個節點必須經由它的子節點才能到達外節點。由於性質 2,一個節點的距離實際上就是這個節點一直沿它的右邊到達一個外節點所經過的邊數,也就是說,我們有
[性質 3] 節點的距離等於它的右子節點的距離加 1。
即 dist( i ) = dist( right( i ) ) + 1 外節點的距離為 0,由於性質 2,它的右子節點必為空節點。為了滿足性質 3,故前面規定空節點的距離為-1。

我們的印象中,平衡樹是具有非常小的深度的,這也意味著到達任何一個節點所經過的邊數很少。左偏樹並不是為了快速訪問所有的節點而設計的,它的目的是快速訪問最小節點以及在對樹修改後快速的恢復堆性質。從圖中我們可以看到它並不平衡,由於性質 2 的緣故,它的結構偏向左側,不過距離的概念和樹的深度並不同,左偏樹並不意味著左子樹的節點數或是深度一定大於右子樹。

下面我們來討論左偏樹的距離和節點數的關系。
[引理 1] 若左偏樹的距離為一定值,則節點數最少的左偏樹是完全二叉樹。
證明:由性質 2 可知,當且僅當對於一棵左偏樹中的每個節點 i,都有dist(left(i)) = dist(right(i)) 時,該左偏樹的節點數最少。顯然具有這樣性質的二叉樹是完全二叉樹。


[定理 1] 若一棵左偏樹的距離為k,則這棵左偏樹至少有 2k+1-1 個節點。
證明:由引理 1 可知,當這樣的左偏樹節點數最少的時候,是一棵完全二叉
樹。距離為k的完全二叉樹高度也為k,節點數為 2k+1-1,所以距離為k的左偏樹
至少有 2k+1
-1 個節點。
作為定理 1 的推論,我們有:
[性質 4] 一棵 N 個節點的左偏樹距離最多為 ?log(N+1)? -1。
證明:設一棵N個節點的左偏樹距離為k,由定理 1 可知,N ≥ 2k+1-1,因此k
≤ ?log(N+1)? -1。
有了上面的 4 個性質,我們可以開始討論左偏樹的操作了

1、左偏樹的合並

C ← Merge(A,B)

下圖是一個合並過程的示例:

技術分享圖片

下面是合並的代碼:

int Merge(int x,int y)//兩棵樹合並
{
    if(!x) return y;//找到插入的地方了
    if(!y) return x;
    if(v[x]<v[y]) swap(x,y);//使得根節點最大
    r[x]=Merge(r[x],y);//遞歸合並右子樹和y
    fa[r[x]]=x;//更新右子樹的根
    if(d[l[x]]<d[r[x]]) swap(l[x],r[x]);
    d[x]=d[r[x]]+1;
    return x;//新的根
}

2、插入新節點:

int Insert(int x, int y)
{
    return Merge(x, Init(y));
}

3、刪除最小節點:

int Pop(int x)
{
    int L=l[x];//取出左子樹
    int R=r[x];//取出右子樹
    fa[L]=L;//根變為自己
    fa[R]=R;
    v[x]/=2;//取根節點 值除二
    r[x]=l[x]=d[x]=0;
    return Merge(L,R);//左右子樹合並  返回值為左右子樹合並後新的根
}

下面看一道例題:

題目鏈接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=1389

Monkey King

Time Limit: 10 Seconds Memory Limit: 32768 KB

Once in a forest, there lived N aggressive monkeys. At the beginning, they each does things in its own way and none of them knows each other. But monkeys can‘t avoid quarrelling, and it only happens between two monkeys who does not know each other. And when it happens, both the two monkeys will invite the strongest friend of them, and duel. Of course, after the duel, the two monkeys and all of their friends knows each other, and the quarrel above will no longer happens between these monkeys even if they have ever conflicted.

Assume that every money has a strongness value, which will be reduced to only half of the original after a duel(that is, 10 will be reduced to 5 and 5 will be reduced to 2).

And we also assume that every monkey knows himself. That is, when he is the strongest one in all of his friends, he himself will go to duel.

Input

There are several test cases, and each case consists of two parts.

First part: The first line contains an integer N(N<=100,000), which indicates the number of monkeys. And then N lines follows. There is one number on each line, indicating the strongness value of ith monkey(<=32768).

Second part: The first line contains an integer M(M<=100,000), which indicates there are M conflicts happened. And then M lines follows, each line of which contains two integers x and y, indicating that there is a conflict between the Xth monkey and Yth.

Output

For each of the conflict, output -1 if the two monkeys know each other, otherwise output the strongness value of the strongest monkey in all friends of them after the duel.

Sample Input

5
20
16
10
10
4
5
2 3
3 4
3 5
4 5
1 5

Sample Output

8
5
5
-1
10

題目大意:

題意: N(N<=10^5)只猴子,初始每只猴子為自己猴群的猴王,每只猴子有一個初始的力量值。這些猴子會有M次會面。每次兩只猴子x,y會面,若x,y屬於同一個猴群輸出-1,否則將x,y所在猴群的猴王的力量值減半,然後合並這兩個猴群。新猴群中力量值最高的為猴王。輸出新猴王的力量值。

分析:涉及集合的查詢,合並,取最值。 利用並查集和左偏樹即可解決。

看代碼:

#include<cstdio>
#include<iostream>
#include<math.h>
using namespace std;
const int maxn=2e5;
int tot,v[maxn],l[maxn],r[maxn],d[maxn],fa[maxn];
int Findset(int x)
{
    if(fa[x]==x) return fa[x];
    return fa[x]=Findset(fa[x]);
}
void Init(int x)//初始化
{
    tot++;
    v[tot]=x;//存對應的值
    fa[tot]=tot;//並查集初始化
    l[tot]=r[tot]=d[tot]=0;//左子樹和右子樹和距離為0
}
int Merge(int x,int y)//兩棵樹合並
{
    if(!x) return y;//找到插入的地方了
    if(!y) return x;
    if(v[x]<v[y]) swap(x,y);//使得根節點最大
    r[x]=Merge(r[x],y);//遞歸合並右子樹和y
    fa[r[x]]=x;//更新右子樹的根
    if(d[l[x]]<d[r[x]]) swap(l[x],r[x]);
    d[x]=d[r[x]]+1;
    return x;//新的根
}
int Pop(int x)
{
    int L=l[x];//取出左子樹
    int R=r[x];//取出右子樹
    fa[L]=L;//根變為自己
    fa[R]=R;
    v[x]/=2;//取根節點 值除二
    r[x]=l[x]=d[x]=0;
    return Merge(L,R);//左右子樹合並  返回值為左右子樹合並後新的根
}
int Top(int x)
{
    return v[x];
}
void solve(int x,int y)
{
    int Left=Pop(x);//去掉x後 左右子樹合並後新的根
    int Right=Pop(y);//去掉y後 左右子樹合並後新的根
    Left=Merge(Left,x);//把更新後的值重新加入該樹中
    Right=Merge(Right,y);
    Left=Merge(Left,Right);//兩棵樹合並
    printf("%d\n",Top(Left));//兩棵樹合並之後的根
}


int main()
{
    int n,m,i,x,y;
    while(scanf("%d",&n)!=EOF)
    {
        tot=0;
        for(i=1;i<=n;i++)
        {
            scanf("%d",&x);
            Init(x);
        }
        scanf("%d",&m);
        for(i=1;i<=m;i++)
        {
            scanf("%d%d",&x,&y);
            int fx=Findset(x);
            int fy=Findset(y);
            if(fx==fy) printf("-1\n");
            else solve(fx,fy);
        }
    }
    return 0;
}

Monkey King(左偏樹 可並堆)