Poj 败方树 解题报告

题目链接:http://dsalgo.openjudge.cn/extsortexec/2/
题目是一个败方树的模板题。借此总结一下败方树的基本知识。(我的知识来自北京大学数据结构与算法mooc,网址:http://www.chinesemooc.org/mooc/4435,符号表示也控制的一致,方便读者理解)

败方树的内部节点记录着比赛的败者在数据数组中的索引(除了0号结点保存这最优者索引),用胜者和父节点比较,在更新重构的时候只需考虑父节点。
败方树的节点分为外部节点内部节点,外部节点存储在数据数组中,内部节点存储在一棵完全二叉树中,当然败方树也是一棵完全二叉树。
然后是编号问题,内部结点只有一个编号,从0开始,从上到下,从左往右;外部结点同时有两种编号一是在败方树中和内部节点一致的编号,二是在数据数组中的下标编号,该编号是对于败方树从左往右(不论在不在同一层)编号的。
下面是一些规律:(n为外部节点数)
首先得到倒数第二层的节点数s,s的寻找方法是平凡的,看代码可以容易看懂;
其次,由s可以找到offset,即除最后一层外的节点个数,公式为

offset=2s1
推导过程如下:
设倒数第二层的高度为n,则s=2^n,最后一层高度为n+1,那么由等比数列求和公式可知offset = 2^(n+1)-1=2*s-1;
随后我们计算出最后一层的外部节点数lowest,公式为
lowest=2(ns)
推导过程如下:
首先n大于s,这是确定的。其次,假设倒数第二层中非外部结点的结
点数为nIn,外部节点数为nOut,则nIn=lowest/2,这里也提一下lowest一定是偶数,原因就不解释了。然后有:
s = nOut + nIn, n = nOut + lowest = s - nIn + lowest = s + lowest/2。
所以有上面的公式。

最后一个内部节点的编号为n-1,公式为:

offsets+nIn=n1(

顺带一提,整棵树一共2n个结点。

下面是代码解释:
比赛(play)过程:假设a和b比赛,它们的父节点是p。败者索引放在其父节点。注意到这个实现中当父节点的标号为偶数时不向上比较,即左路不向上比较,而且将胜者索引放在父节点的父节点处。这样一来,由于建树过程是从下向上,从左往右,在右路向上的时候在内部节点处比较的是其两棵子树的最优者。

重构(replay)过程:
首先要确定父节点的位置,分的情况是改变的点在最后一层和不在最后一层,判断外部节点在数据数组中的编号。然后将每一次与父节点比较的结果放在0号内部节点;这里要说明的是被更新的数据是原本的全局最优者,明确这一点后,我们知道每一个内部节点中的索引其实是以该节点为根的原本的次优者或最优者(在从被改变结点到树根的路径上的内部节点保存的是次优者),所以一直向上比较能够找出新的全局最优者。

建树过程:
在看完了上面的“一些规律”之后,这一部分我相信是简单的。

代码如下:

#include 
#include 

using namespace std;

int *points, *tree;
int numOfNodeLast, offset;

int winner(int x, int y) {
    return points[x] < points[y] ? x : y;
}

int loser(int x, int y) {
    return points[x] < points[y] ? y : x;
}

void play(int p, int x, int y) {
    tree[p] = loser(x, y);
    int temp1, temp2;
    temp1 = winner(x, y);
    while(p > 1 && p%2) {
       temp2 = winner(temp1, tree[p/2]);
       tree[p/2] = loser(temp1, tree[p/2]);
       temp1 = temp2;
       p /= 2;
    }
    tree[p/2] = temp1;
}

void bulid(int n) {
    int sizeOfSubtree, i;
    for(sizeOfSubtree = 1; 2 * sizeOfSubtree < n; sizeOfSubtree *= 2) {}
    offset = 2*sizeOfSubtree - 1;
    numOfNodeLast = 2*(n - sizeOfSubtree);

    //外部节点间比较
    for(i = 1; i < numOfNodeLast; i += 2) {
        play((offset+i)/2, i-1, i);
    }
    //需要内部和外部比较的那一个外部节点可能存在
    if(n%2 == 1) {
        play((n-1)/2, tree[n-1], numOfNodeLast);
        i = numOfNodeLast + 2;
    } else {
        i = numOfNodeLast + 1;
    }
    //其它外部节点比较
    for(; i <= n; i += 2) {
        play((i-numOfNodeLast+n-1)/2, i-1, i);
    }
}

void replay(int x, int n) {
    int p;
    if(x < numOfNodeLast) {
        p = (x + offset) / 2;
    } else {
        p = (x - numOfNodeLast + n - 1) / 2;
    }
    tree[0] = winner(x, tree[p]);
    tree[p] = loser(x, tree[p]);
    int temp;
    for(; p/2 >= 1; p /= 2) {
        temp = winner(tree[p/2], tree[0]);
        tree[p/2] = loser(tree[p/2], tree[0]);
        tree[0] = temp;
    }
}

int main()
{
    int n, m, pos, ele;
    cin >> n >> m;
    points = new int[n];
    tree = new int[n];
    for(int i = 0; i < n; ++i) {
        cin >> points[i];
    }
    bulid(n);
    for(int e = 0; e < n; ++e) {
            cout << points[tree[e]] << " ";
    }
    cout << endl;
    for(int i = 0; i < m; ++i) {
        cin >> pos >> ele;
        points[pos] = ele;
        replay(pos, n);
        for(int e = 0; e < n; ++e) {
            cout << points[tree[e]] << " ";
        }
        cout << endl;
    }
    delete [] points;
    delete [] tree;
    return 0;
}

你可能感兴趣的:(c++)