[海军国际项目办公室]AVL 树

本文详细介绍了AVL树的基本概念,重点讲解了在AVL树中插入节点并保持树平衡的过程。通过中序遍历最小化字典序的选择策略,以及如何计算维护AVL形态所需的节点数量。文章使用动态规划和递归的方法,讨论了节点插入后的高度更新和平衡因子调整,展示了如何在O(nlog²n)的时间复杂度内完成这一过程。同时,给出了相应的C++实现代码,帮助读者理解算法细节。
摘要由CSDN通过智能技术生成

AVL 树

题目描述

在这里插入图片描述
在这里插入图片描述

题解

由于原树 A V L AVL AVL是一棵二叉查找树,所以我们要让留下的 K K K个节点的中序遍历字典序最小,相当于让我们保留下来的所有点的字典序竟可能小。
所以我们可以考虑从 1 − n 1-n 1n一直枚举点 i i i,看点 i i i在不改变编号比它小的点的加入状态的情况下是否能被加入。
如果一个点能被加入,就将它加入,在其前缀已确定的情况下,它加入肯定比它比加入优秀。

显然,如果我们要让这个点加入,我们肯定还要一部分点加入以维护整棵树的 A V L AVL AVL形态。
如果加入这个点后需要加入以维护形态的点数加上已加入的点数比 K K K大,那肯定是不行了。
我们考虑如何计算维护形态的点的数量。
我们定义 d p x , i dp_{x,i} dpx,i表示要让以 x x x为根的子树中被选中的点所构成的树是一棵高度为 i i i A V L AVL AVL树,我们至少需要加入的未加入的点的数量, f i f_{i} fi表示一棵高度为 i i i A V L AVL AVL最少需要的点数。
显然有转移方程式, f i = f i − 1 + f i − 2 + 1 f_{i}=f_{i-1}+f_{i-2}+1 fi=fi1+fi2+1
显然, d p x , i dp_{x,i} dpx,i最开始为 f i f_{i} fi
我们再定义 h x h_{x} hx表示根据 x x x子树内的情况来看, h h h的高度最低为多少。
由于 d p , h dp,h dp,h都需要根据子树内的状况来进行跟更改,所以我们考虑我们每加入一个节点时会对哪些加点造成影响。
显然,只有我们加入节点到根节点的链上会被影响,而 A V L AVL AVL树的高度是 log ⁡   n \log\,n logn级别的,所以需要我们更改 d p dp dp值的节点也是 log ⁡   n \log\,n logn级别的。
h h h的转移式: h x = max ⁡ ( h l s o n , h r s o n ) + 1 h_{x}=\max(h_{lson},h_{rson})+1 hx=max(hlson,hrson)+1
h h h,由于 h h h表示的是最低高度,所以它的最低高度只需要比最低高度最高的儿子的最低高度高即可。
但如果只看最低高度的话不一定是一棵合法的 A V L AVL AVL树,所以我们显然不能直接将两棵树的大小加在一起。
我们明显可以用 d p dp dp来将整棵子树的最低高度以上的可能的高度所需要的 s i z siz siz值给存下来,方便转移,有 d p dp dp转移式,
d p u , i = min ⁡ ( d p l s o n , i − 1 + d p r s o n , i − 1 , d p l s o n , i − 2 + d p r s o n , i − 1 , d p l s o n , i − 1 + d p r s o n , i − 2 ) + 1 dp_{u,i}=\min(dp_{lson,i-1}+dp_{rson,i-1},dp_{lson,i-2}+dp_{rson,i-1},dp_{lson,i-1}+dp_{rson,i-2})+1 dpu,i=min(dplson,i1+dprson,i1,dplson,i2+dprson,i1,dplson,i1+dprson,i2)+1
由于我们的根 r o o t root root上面就没有其它节点了,所以只要 d p r o o t , h r o o t dp_{root,h_{root}} dproot,hroot加上已经加入的节点数是小于 K K K的就可以了。
我们的 d p dp dp值是在满足所以已有条件的情况下够着出一种合法情况还需要加入节点的数量,显然,对于 i < h x i<h_{x} i<hx d p x , i dp_{x,i} dpx,i,它们的值都是极大的,这部分根本不能转移,而如果 i i i大于该子树的最大高度的话,也显然是不能转移的,也赋为极大值。
如果当前节点不能加入,我们将刚刚一条链的转移都撤回就行了。
很明显,我们这样一直构造下去得到的情况一定是最小的合法情况。

事实上如果你愿意从上到下时直接维护该子树至少需要多高的话也就没必要维护这个了,在这种情况下,我们是能让较高的子树往左传就往左传,否则就往右传。
但显然 d p dp dp的打法明显是更简单的。

时间复杂度 O ( n log ⁡ 2   n ) O\left(n\log^2\,n\right) O(nlog2n),但还是可以轻松的过掉。

源码

#include<bits/stdc++.h>
using namespace std;
#define MAXN 500005
#define lowbit(x) (x&-x)
#define reg register
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
typedef long long LL;
typedef unsigned long long uLL;     
const LL INF=0x3f3f3f3f3f3f3f3f;  
const int mo=998244353;
const int inv2=499122177;
const int jzm=2333;
const int n1=50;
const int zero=10000;
const int orG=3,invG=332748118;
const double Pi=acos(-1.0);
const double eps=1e-5;
typedef pair<LL,int> pii;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
    _T f=1;x=0;char s=getchar();
    while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
    while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
    x*=f;
}
template<typename _T>
void print(_T x){putchar('\n');while(x>9){putchar((x%10)|'0');x/=10;}putchar(x|'0');}
LL gcd(LL a,LL b){return !b?a:gcd(b,a%b);}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1LL)t=1ll*a*t%p;a=1ll*a*a%p;s>>=1LL;}return t;}
int n,K,lson[MAXN],rson[MAXN],dp[30],root,num;
int h[MAXN],tp,hei[MAXN],f[MAXN][30];
int stak,sta[30],st[30][30],sth[30];
bool ans[MAXN],fg;
void sakura(int rt){
    if(lson[rt])sakura(lson[rt]);
    if(rson[rt])sakura(rson[rt]);
    hei[rt]=max(hei[lson[rt]],hei[rson[rt]])+1;
} 
void insert(int rt,int x){
    sta[++stak]=rt;sth[stak]=h[rt];
    for(int i=0;i<=hei[rt];i++)st[stak][i]=f[rt][i];
    if(rt==x){
        f[rt][0]=n+1;f[rt][1]=f[lson[rt]][0]+f[rson[rt]][0];
        h[rt]=max(h[lson[rt]],h[rson[rt]])+1;
        for(int i=0;i<=hei[rt];i++)f[rt][i]=n+1;
        for(int i=h[rt];i<=hei[rt];i++)
            f[rt][i]=min(f[lson[rt]][i-1]+f[rson[rt]][i-1],f[rt][i]),
            f[rt][i]=min(f[lson[rt]][i-1]+f[rson[rt]][i-2],f[rt][i]),
            f[rt][i]=min(f[lson[rt]][i-2]+f[rson[rt]][i-1],f[rt][i]);
        return ;
    }
    if(x<rt)insert(lson[rt],x);else insert(rson[rt],x);
    h[rt]=max(h[lson[rt]],h[rson[rt]])+1;
    for(int i=0;i<=hei[rt];i++)f[rt][i]=n+1;
    for(int i=h[rt];i<=hei[rt];i++)
        f[rt][i]=min(f[lson[rt]][i-1]+f[rson[rt]][i-1]+(rt>x),f[rt][i]),
        f[rt][i]=min(f[lson[rt]][i-1]+f[rson[rt]][i-2]+(rt>x),f[rt][i]),
        f[rt][i]=min(f[lson[rt]][i-2]+f[rson[rt]][i-1]+(rt>x),f[rt][i]);
}
signed main(){
    freopen("avl.in","r",stdin);
    freopen("avl.out","w",stdout);
    read(n);read(K);dp[1]=1;dp[2]=2;int tim=0;
    for(int i=3;i<=28;i++)dp[i]=dp[i-1]+dp[i-2]+1;
    for(int i=1;i<=n;i++){
        int x;read(x);if(x<0){root=i;continue;}
        if(i<x)lson[x]=i;else rson[x]=i;
    }
    sakura(root);
    for(int i=0;i<=n;i++)
        for(int j=0;j<=28;j++)f[i][j]=n+1;
    f[0][0]=0;
    for(int i=1;i<=n;i++)
        for(int j=0;j<=hei[i];j++)f[i][j]=dp[j];
    for(int i=1;i<=n;i++){
        stak=0;insert(root,i);
        if(num+1+f[root][h[root]]>K){
            while(stak){
                h[sta[stak]]=sth[stak];
                for(int j=0;j<=hei[sta[stak]];j++)
                    f[sta[stak]][j]=st[stak][j];
                stak--;
            }
        }
        else ans[i]=1,num++;
    }
    for(int i=1;i<=n;i++)assert(Fabs(h[lson[i]]-h[rson[i]])<=1);
    for(int i=1;i<=n;i++)printf("%d",ans[i]);puts("");
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值