bzoj1078: [SCOI2008]斜堆 构造

2 篇文章 0 订阅

Description
  斜堆(skew heap)是一种常用的数据结构。它也是二叉树,且满足与二叉堆相同的堆性质:每个非根结点的值
都比它父亲大。因此在整棵斜堆中,根的值最小。但斜堆不必是平衡的,每个结点的左右儿子的大小关系也没有任
何规定。在本题中,斜堆中各个元素的值均不相同。 在斜堆H中插入新元素X的过程是递归进行的:当H为空或者X
小于H的根结点时X变为新的树根,而原来的树根(如果有的话)变为X的左儿子。当X大于H的根结点时,H根结点的
两棵子树交换,而X(递归)插入到交换后的左子树中。 给出一棵斜堆,包含值为0~n的结点各一次。求一个结点
序列,使得该斜堆可以通过在空树中依次插入这些结点得到。如果答案不惟一,输出字典序最小的解。输入保证有
解。
HINT n<=200 , 其实复杂度为O(n ^ 2)

题解:(from Czarina)
性质1:若一个节点没有左儿子,则该节点一定没有右儿子。

    因为右儿子是由左儿子旋转得到的,而在旋转的同时左边一定被插入节点了。

性质2:最后一次插入一定插到极左节点(一直往左走),显然得证。

性质3:最后一次插入节点一定没有右子树,因为它在插入时一定是将原来的某棵子树(可以为空)作为它的左子树。

所以最后插入的节点一定是满足性质2,3的深度最小的节点,删除该点后将其父亲到跟的左右子树交换。

因为如果不选深度最小的,则在交换的时候一定会交换到没有右子树的点,不满足性质1。

特例:若深度最小的满足性质2,3的节点有左儿子且其左儿子为叶子节点,则选其左儿子(字典序最小)

   因为删除其左儿子后该点左右子树为空,满足性质1

所以就不断找最后加入的节点即可,记得考虑在删除根后的影响,还有边界的判定(编号从0开始要特判,我把0号节点变成了n + 1号)
**删除一个节点不但要将fa的儿子更新,还要将儿子的fa更新
提高代码能力,这种题应该1A
造数据可以自己构造一个序列,然后输出插入后的斜堆,保证一定有解。
思路:找性质,发现从后往前构造状态是确定的。构造题应该多找性质!**

#include<bits/stdc++.h>
using namespace std;
#define maxn 320
#define ls(x) ch[x][0]
#define rs(x) ch[x][1]
#define rep(i,l,r) for (int i = l ; i <= r ; i++)

int fa[maxn],ch[maxn][2];
int n,a[maxn],tot,root;

inline bool isleaf(int x){
    return !ls(x) && !rs(x) && x;
}
int dfs(int x){
    if ( isleaf(x) ) return x;
    if ( !rs(x) && !isleaf(ls(x)) ) return x;
    return dfs(ls(x));
}
int main(){
    freopen("input.txt","r",stdin);
    scanf("%d",&n);
    for (int i = 1 ; i <= n ; i++){
        scanf("%d",&fa[i]);
        if ( fa[i] >= 100 ){
            fa[i] -= 100;
            if ( !fa[i] ) fa[i] = n + 1;
            rs(fa[i]) = i;
        }
        else{
            if ( !fa[i] ) fa[i] = n + 1;
            ls(fa[i]) = i;
        }
    }
    root = n + 1;
    for (int i = 1 ; i <= n + 1 ; i++){
        int x = dfs(root);
        a[i] = x;
        if ( x == root ) root = ls(root) , fa[root] = 0;
        else{
            ls(fa[x]) = ls(x) , fa[ls(x)] = fa[x];
            x = fa[x];
            while ( x ){
                swap(ls(x),rs(x));
                x = fa[x];
            }
        }
    }
    for (int i = n + 1 ; i >= 1 ; i--){
        if ( a[i] == n + 1 ) a[i] = 0;
        printf("%d ",a[i]);
        //if ( i > 1 ) printf(" ");
    }
    printf("\n");
}

附上datamaker

#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define maxn 120

int ls[maxn],rs[maxn],fa[maxn],a[maxn],root;
int insert(int x,int y){
    if ( !x ) return y;
    if ( y < x ){
        ls[y] = x;
        return y;
    }
    swap(ls[x],rs[x]);
    ls[x] = insert(ls[x],y);
    return x;
}
int main(){
    freopen("1.cnt","r",stdin);
    int cnt;
    scanf("%d",&cnt);
    fclose(stdin);
    freopen("1.cnt","w",stdout);
    cout<<++cnt;
    fclose(stdout);
    srand(cnt + time(0));
    freopen("input.txt","w",stdout);
    int n = 7,k = rand() % 3 + 2;
    cout<<n<<endl;
    for (int i = 1 ; i <= n + 1 ; i++) a[i] = i;
    random_shuffle(a + 1,a + n + 2);
//  root = 1;
    for (int i = 1 ; i <= n + 1 ; i++){
        root = insert(root,a[i]);
    }
    for (int i = 1 ; i <= n + 1 ; i++){
        if ( ls[i] ) fa[ls[i]] = i;
        if ( rs[i] ) fa[rs[i]] = i + 100;
    }
    for (int i = 2 ; i <= n + 1 ; i++){
        cout<<fa[i] - 1<<" ";
    }
    cout<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值