数据结构专题 - 解题报告 - I

这篇博客探讨了如何使用平衡树解决特定问题,尤其是利用二叉搜索树的特性进行排名查询。作者提到了在保持查询效率的同时,通过Treap(堆+树)来避免树的失衡。文章详细介绍了Treap的基本操作,如插入、删除和旋转,并提供了AC代码作为示例。同时,作者也尝试了使用set的方法,发现其本质上也是平衡树,虽然实现简单,但在性能上与手写平衡树相差不大。

平衡树做法,因为最近正好自己看了点平衡树相关的知识,便觉得这个题就是专门为了平衡树准备的。(完全没想到set,其实也是不会用)
因为查排名,如果我们有一棵二叉搜索树那就会很好办,每个点都有相应的判断位置的值,从根节点入手,如果当前节点比过题数较多就往左儿子查,过题少就往右儿子查,相等就是判断罚分然后向左或者向右儿子搜索。一旦发现与当前节点过题数和罚分都相等的节点就返回左方所有节点囊括的点数,在那基础上加一就是需要的解。
为了维持二叉搜索树查询的效率,我们需要平衡树,我暂时只会treap(就是堆+树),通过堆的性质维护使BST不至于失衡。代码里应该都是treap基本的操作:插入,删除,树枝的旋转(这个学了好久)。然后自写个递归查询即可。
至于treap的维护,每一次读入数据后删去该点本身在书中的数据,更新后再插入即可。查询打印
具体见AC代码注释:(因为是初学平衡树,码的太丑别嫌弃www)

#include<bits/stdc++.h>
#define maxn 10000005
#define maxm 30000005
#define FOR(a, b, c) for(int a=b; a<=c; a++)
#define qhqh 1000000007
#define inf 2147483647
#define llinf 9223372036854775807
#define ll long long
#define pi acos(-1.0)
#define ls ch[p][0]
#define rs ch[p][1]

using namespace std;

//快读
inline int read()
{
    char c=getchar();long long x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}

int n, m, x, penalty;
int ch[maxn][2];			//左右儿子
int dat[maxn];
int siz[maxn], cnt[maxn]; 		//siz[]表示以当前节点为根的子树大小,cnt[ ]表示当前节点包含的副本数
int root, tot;
struct node
{
    int x, p;
}val[maxn], rua[maxn];		//val[ ]代表树上的权值,rua[ ]是每个队伍的当前的权值

int New(int v, int penalty)			//新建节点
{
    val[++tot].x = v;
    val[tot].p = penalty;		
    dat[tot] = rand();			//新建节点时随机出优先级,防止BST退化成一条链
    siz[tot] = 1;					
    cnt[tot] = 1;
    return tot;
}

void pushup(int p)
{
    siz[p] = siz[ls] + siz[rs] + cnt[p];			//本节点子树大小 = 左节点大小 + 右节点大小 + 本节点副本数
}

void build()
{
    root = New(inf/10, 0);		
    ch[root][1] = New(-inf/10, 0);			//先插入正无穷和负无穷处理边界(除以10啥的不用管)
    pushup(root);
}

void Rotate(int &p, int d)				//d表示旋转方向,0表示左旋,1表示右旋
{
    int temp = ch[p][d^1];
    ch[p][d^1] = ch[temp][d];			
    ch[temp][d] = p;					
    p = temp;
    pushup(ch[p][d]);
    pushup(p);
}
//做题理解:旋转的实质是({在满足BST的基础上比较优先级},通过交换本节点和某个叶子节点)把链叉开成为二叉形状,从而控制深度

void Insert(int &p, int v, int penalty)
{
    if(!p)			//若p节点为空,新建
    {
        p = New(v, penalty);
        return;
    }
    if(v == val[p].x && penalty == val[p].p)
        cnt[p]++;			//节点已存在,那副本++
    else					//BST,大的插左边,小的插右边
    {
        int d;
        if(v == val[p].x)
            d = penalty < val[p].p ? 0 : 1;		//控制方向
        else
            d = v > val[p].x ? 0 : 1;
        Insert(ch[p][d], v, penalty);			//递归实现插入
        if(dat[p] < dat[ch[p][d]])
            Rotate(p, d^1);			//与左节点交换就是右旋,否则左旋
    }
    pushup(p);		//每次都要更新本节点信息
}

void Remove(int &p, int v, int penalty)
{
    if(!p) return;
    if(v == val[p].x && penalty == val[p].p)
    {
        if(cnt[p] > 1)		//若删除的节点数量大于1,则单纯副本数 - - 即可
        {
            cnt[p]--;
            pushup(p);		//更新
            return;
        }
        if(ls || rs)				//存在儿子节点
        {
            if(!rs || dat[ls] > dat[rs])			//当前节点被移走后会有新节点补上来,左儿子或者右儿子,这个按照优先级补上(堆维护树形)
            {
                Rotate(p, 1);				//右旋就是与左儿子交换,并使当前节点变成右节点
                Remove(rs, v, penalty);
            }
            else
            {
                Rotate(p, 0);				//左旋
                Remove(ls, v, penalty);
            }
            pushup(p);
        }
        else
            p = 0;			//如果本节点是叶子节点,直接删除
        return;
    }
    //以下是继续BST的过程
    if(v == val[p].x)
        penalty < val[p].p ? Remove(ls, v, penalty) : Remove(rs, v, penalty);
    else
        v > val[p].x ? Remove(ls, v, penalty) : Remove(rs, v, penalty);
    pushup(p);
}

int get_rank(int p, int v, int penalty)		//查询函数
{
    if(!p) return 0;
    if(v == val[p].x)
    {
        if(penalty == val[p].p)
            return siz[ls]+1;		//如果找到,此时左边所有子树排位都比他高,他的排名为左儿子的siz+1
        if(penalty < val[p].p)
            return get_rank(ls, v, penalty);		//搜左儿子
        else
            return siz[ls] + cnt[p] + get_rank(rs, v, penalty);			//搜右儿子要加上当前的左儿子大小和节点p的副本数
    }
    else if(v > val[p].x)
        return get_rank(ls, v, penalty);
    else
        return siz[ls] + cnt[p] + get_rank(rs, v, penalty);
}

int main()
{
    build();
    n = read();
    m = read();
    FOR(i, 1, n)
    {
        rua[i].x = 0;
        rua[i].p = 0;
        Insert(root, 0, 0);
    }
    FOR(i, 1, m)
    {
        x = read();
        penalty = read();
        Remove(root, rua[x].x, rua[x].p);			//拿出来修改
        rua[x].x++;
        rua[x].p += penalty;
        Insert(root, rua[x].x, rua[x].p);				//再插入,让他自己去平衡
        int ans = get_rank(root, rua[1].x, rua[1].p) - 1;		//插入后的rank
        printf("%d\n", ans);			//打印
        //FOR(j, 1, n) printf("j = %d rank = %d\n", j, get_rank(root, rua[j].x, rua[j].p)-1); cout<<endl;
    }
    //FOR(i, 1, tot) printf("%d %d %d\n", i, val[i].x, val[i].p);
    return 0;
}

当然这题的set做法我也尝试写了一下:
手写平衡树并没有跑的比set跑的快多少,毕竟set本质也是平衡树。但是这样模拟进去其实很好写。重载一下运算符就丢给set自己搞了。

#include<bits/stdc++.h>
#define maxn 100005
#define maxm 200005
#define FOR(a, b, c) for(int a=b; a<=c; a++)
#define hrdg 1000000007
#define inf 2147483647
#define llinf 9223372036854775807
#define ll long long
#define pi acos(-1.0)
#define ls p<<1
#define rs p<<1|1
using namespace std;

int n, m, t, p, Rank=1;

inline int read()
{
    char c=getchar();long long x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}

struct node//x过题数,penalty罚时
{
    int x, penalty;
}tmp, a[maxn];

multiset <node> s;
bool operator < (const node u,const node v)     //重载运算符
{
    return u.x==v.x ? u.penalty > v.penalty : u.x < v.x;
}
bool operator >= (const node u,const node v)
{
    return u.x==v.x ? u.penalty <= v.penalty : u.x >= v.x;
}
multiset<node>::iterator i1, i2;//两个迭代器

int main()
{
    n=read(), m=read();
    FOR(i, 1, m)
    {
        t=read(), p=read();
        tmp = a[t];//记录他之前的值
        a[t].x++;
        a[t].penalty += p;
        if(t == 1)
        {
            i1 = s.begin();				//记录最小
            while(s.size() && a[1] >= (*i1))//如果你的排名满足高于他的条件且还有人排名比你高
            {
                i2 = i1;
                ++i1;
                s.erase(i2);//丢出来
                Rank--;
            }
        }
        else if(a[1] < a[t])
        {
            if(a[1] < tmp)			//当他的排名原本就比你高
            {
                s.erase(s.find(tmp));		//则更新他的信息
                Rank--;
            }
            s.insert(a[t]);			//如果他原来没你高,但是现在比你高,则计入
            Rank++;
        }
        printf("%d\n", Rank);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值