首页 > 其他分享 >浅谈替罪羊树

浅谈替罪羊树

时间:2022-10-12 09:46:25浏览次数:68  
标签:ch return 浅谈 int sizv 替罪羊 else num

前言

噫,好!我又来了

这几天闲着没事翻桃片,发现好多人卡评测被封了,就看了看紫荆花,发现 splay 会被卡,就去学了替罪羊树(\(\text{Scapegoat Tree}\))。

正文

写在前面

大家都知道,splay 是通过旋转维护整棵树的平衡,但也会出现深度过大而导致复杂度爆炸的情况。

而替罪羊树则是通过重构来维护平衡,他会初始一个失衡系数 \(\alpha\),一般取 \([0.7,0.8]\),然后通过比较左右子树大小和自身大小来判断是否失衡,若失衡,就拍扁重构

然后是关于删除的一个问题,替罪羊树用的是惰性删除(把当前节点个数清零,但不从树中删去),记住这个东西,对之后的很多东西是都有影响的。

关于结构体

struct Node {
	int val,num,ch[2],sizt,sizv,siznd;
}t[2000001];

可以发现,基本上与 splay 没有什么太大区别,但多了,三个 siz 并且没有 fa(因为他不需要遍历父亲)。

解读一下这三个 siz。

  1. sizt:子树中有多少个不同的权值(包括空节点)

  2. sizv:子树中有多少个节点(不包括空节点)

  3. siznd:子树中有多少个不同的权值(不包括空节点)

对应的 upd 应该这么写

void upd(int x) {
	t[x].sizt=t[t[x].ch[0]].sizt+t[t[x].ch[1]].sizt+1;
	t[x].sizv=t[t[x].ch[0]].sizv+t[t[x].ch[1]].sizv+t[x].num;
	t[x].siznd=t[t[x].ch[0]].siznd+t[t[x].ch[1]].siznd+(t[x].num>=1);
}

关于重构

就是把一棵树展开进一个线性表中,再二分建树(空节点不建树),比较简单,直接上代码了。

void rbuflatten(int x) {
	if (!x) return;
	rbuflatten(t[x].ch[0]);
	if (t[x].num) lur[++luc]=x;
	rbuflatten(t[x].ch[1]);
}
    
int rbubuild(int l,int r) {
	if (l==r) return 0;
	int mid=(l+r>>1);
	t[lur[mid]].ch[0]=rbubuild(l,mid);
	t[lur[mid]].ch[1]=rbubuild(mid+1,r);
	upd(lur[mid]);
	return lur[mid];
}

void rbu(int &x) { luc=0,rbuflatten(x),x=rbubuild(1,luc+1); }

判断是否重构有三个条件

  1. 本节点是否为空

  2. 自己的 sizt 乘上 \(\alpha\) 是否小于左右子树中最大的子树的 sizt

  3. 自己的 sizt 乘上 \(\alpha\) 是否大于自己的 siznd

关于第三条,因为惰性删除,删除操作一旦过多,会导致树中的空节点过多,显而易见,空节点多没有用,所以,可通过重构操作删去。

bool ifrbu(int x) { return (t[x].num and ((t[x].sizt*A<=(double)std::max(t[t[x].ch[0]].sizt,t[t[x].ch[1]].sizt)) or (t[x].sizt*A>=(double)t[x].siznd))); }

查询排名

替罪羊树有两个函数,一个为 rkf,是查询比 \(k\) 小的数中最大数的排名,一个是 rkl,是查询比 \(k\) 大的数中最小数的排名。

两个函数写法基本相同,\(k\) 比本节点大往右子树搜,比本节点小往左子树搜。

但是!替罪羊树采用惰性删除,所以显而易见的,当 \(k\) 等于本节点时如果 num 不等于 \(0\) 是可以返回的,但如果等于 \(0\) 呢?

两个函数有两个不同的写法。

对于 rkf 因为他是要找比 \(k\) 小的数中的最大数的排名,所以他的返回值为本节点的左子树的 sizv,那如果 \(k\) 所代表的节点的 num 为 \(0\),那么他的左子树的 sizv,就等于比 \(k\) 大的数中的最小数所代表节点的左子树的 sizv,所以应该往右子树搜。

对于 rkl 因为他是要找比 \(k\) 大的数中的最小数的排名,所以他的返回值应为本节点的左子树的 sizv 加上自己的 num 再加 \(1\),那如果 \(k\) 所代表的节点的 num 为 \(0\),那么他的左子树大小,就等于比他小的数中最大数所代表节点的左子树的 sizv 加上自己的 num,所以要往左子树搜。

代码如下:

int rkf(int x,int k) {
	if (!x) return 0;
	else {
		if (t[x].val==k and t[x].num) return t[t[x].ch[0]].sizv;
		else if (k<t[x].val) return rkf(t[x].ch[0],k);
		else return rkf(t[x].ch[1],k)+t[t[x].ch[0]].sizv+t[x].num;
	}
}

int rkl(int x,int k) {
	if (!x) return 1;
	else {
		if (t[x].val==k and t[x].num) return t[t[x].ch[0]].sizv+t[x].num+1;
		else if (k>t[x].val) return rkl(t[x].ch[1],k)+t[t[x].ch[0]].sizv+t[x].num;
		else return rkl(t[x].ch[0],k);
	}
}

查询排名为 \(k\) 的权值

没什么好说的,查看 \(k\) 是否在 \((sizv_{\text{左子树}},sizv_{\text{左子树}}+num_{\text{本节点}}]\) 即可,如果小往左子树搜,大则往右子树搜。

int kth(int x,int k) {
	if (!x) return 0;
	else {
		if (t[t[x].ch[0]].sizv<k and k<=t[t[x].ch[0]].sizv+t[x].num) return t[x].val;
		else if (t[t[x].ch[0]].sizv>=k) return kth(t[x].ch[0],k);
		else return kth(t[x].ch[1],k-t[t[x].ch[0]].sizv-t[x].num);
	}
}

前驱以及后继

结合一下上面的三个函数即可。

int pre(int x) { return kth(rt,rkf(rt,x)); }

int nxt(int x) { return kth(rt,rkl(rt,x)); }

插入

比较简单,递归插入即可,不多说了。

回溯时记得重构。

void ins(int &x,int k) {
	if (!x) {
		t[x=++cnt].val=k,t[x].num++;
		upd(x);
		return;
	} else {
		if (k==t[x].val) t[x].num++;
		else if (k>t[x].val) ins(t[x].ch[1],k);
		else ins(t[x].ch[0],k);
		upd(x);
		if (ifrbu(x)) rbu(x);
	}
}

删除

同上

void del(int &x,int k) {
	if (!x) return;
	if (k==t[x].val) t[x].num--;
	else if (k>t[x].val) del(t[x].ch[1],k);
	else del(t[x].ch[0],k);
	upd(x);
	if (ifrbu(x)) rbu(x);
}

例题

以后没准会更新,但 \(99.9\%\) 会咕。

【模板】普通平衡树

板子。

#include <iostream>
#include <cstdio>

namespace Scapegoat_tree {
    const double A=0.75;

    struct Node {
        int val,ch[2],num,sizt,sizv,siznd;
    }t[100001];

    int cnt,rt,ldc,ldr[100001];

    void upd(int x) {
        t[x].sizt=t[t[x].ch[0]].sizt+t[t[x].ch[1]].sizt+1;
        t[x].sizv=t[t[x].ch[0]].sizv+t[t[x].ch[1]].sizv+t[x].num;
        t[x].siznd=t[t[x].ch[0]].siznd+t[t[x].ch[1]].siznd+(t[x].num>=1);
    }

    bool ifrbu(int x) { return (t[x].num and ((t[x].sizt*A<=(double)std::max(t[t[x].ch[0]].sizt,t[t[x].ch[1]].sizt)) or (t[x].sizt*A>=(double)t[x].siznd))); }

    void rbuflatten(int x) {
        if (!x) return;
        rbuflatten(t[x].ch[0]);
        if (t[x].num) ldr[++ldc]=x;
        rbuflatten(t[x].ch[1]);
    }

    int rbubuild(int l,int r) {
        if (l>=r) return 0;
        int mid=(l+r>>1);
        t[ldr[mid]].ch[0]=rbubuild(l,mid);
        t[ldr[mid]].ch[1]=rbubuild(mid+1,r);
        upd(ldr[mid]);
        return ldr[mid];
    }

    void rbu(int &x) {
        ldc=0;
        rbuflatten(x);
        x=rbubuild(1,ldc+1);
    }

    void ins(int &x,int k) {
        if (!x) {
            t[x=++cnt].val=k,t[x].num++;
            upd(x);
        } else {
            if (k==t[x].val) t[x].num++;
            else if (k>t[x].val) ins(t[x].ch[1],k);
            else ins(t[x].ch[0],k);
            upd(x);
            if (ifrbu(x)) rbu(x);
        }
    }

    void del(int &x,int k) {
        if (!x) return;
        if (t[x].val==k) t[x].num--;
        else if (k>t[x].val) del(t[x].ch[1],k);
        else del(t[x].ch[0],k);
        upd(x);
        if (ifrbu(x)) rbu(x);
    }

    int rkf(int x,int k) {
        if (!x) return 0;
        else {
            if (t[x].num and t[x].val==k) return t[t[x].ch[0]].sizv;
            else if (t[x].val>k) return rkf(t[x].ch[0],k);
            else if (t[x].val<=k) return t[t[x].ch[0]].sizv+t[x].num+rkf(t[x].ch[1],k); 
        }
    }

    int rkl(int x,int k) {
        if (!x) return 1;
        else {
            if (t[x].num and t[x].val==k) return t[t[x].ch[0]].sizv+t[x].num+1;else if (t[x].val<k) return t[t[x].ch[0]].sizv+t[x].num+rkl(t[x].ch[1],k);
            else/* if (t[x].val>k) */return rkl(t[x].ch[0],k);
             
        }
        return 1;
    }

    int kth(int x,int k) {
        if (!x) return 0;
        else {
            if (/*t[x].num and */t[t[x].ch[0]].sizv<k and k<=t[t[x].ch[0]].sizv+t[x].num) return t[x].val;
            else if (t[t[x].ch[0]].sizv>=k) return kth(t[x].ch[0],k);
            else return kth(t[x].ch[1],k-t[t[x].ch[0]].sizv-t[x].num);
        }
    }

    int pre(int x) {
        int ra=rkf(rt,x);
        return kth(rt,ra);
    }

    int nxt(int x) {
        int ra=rkl(rt,x);
        return kth(rt,ra);
    }
}
using namespace Scapegoat_tree;
using namespace std;
int n;

void work(int x) {
    if (!x) return;
    work(t[x].ch[0]);
    if (t[x].num) printf("%d ",t[x].val);
    work(t[x].ch[1]);
}

int main() {
    scanf("%d",&n);
    for (int i=1,opt,x;i<=n;i++) {
        scanf("%d%d",&opt,&x);
        if (opt==1) {
            ins(rt,x);
        } else if (opt==2) {
            del(rt,x);
        } else if (opt==3) {
            printf("%d\n",rkf(rt,x)+1);
        } else if (opt==4) {
            printf("%d\n",kth(rt,x));
        } else if (opt==5) {
            printf("%d\n",pre(x));
        } else {
            printf("%d\n",nxt(x));
        }
    }
}

【模板】普通平衡树(数据加强版)

依旧是板子(代码就不放了),记得特判第一次 \(3,4,5,6\) 的操作。

标签:ch,return,浅谈,int,sizv,替罪羊,else,num
From: https://www.cnblogs.com/NtYester/p/16783421.html

相关文章

  • 浅谈MySQL、Hadoop、BigTable、Clickhouse数据读写机制
    个人理解,欢迎指正数据库引擎写数据读数据补充MySqlInnoDB:支持事务,高速读写性能一般Myisam:不支持事务,高速读写性能好以InnoDB更新一条记录为例1、B+Tree......
  • 浅谈LIS和LCS
    这是一篇由超级菜的OIer写的博客......LIS就是最长上升子序列,通过DP求解。普通的求法是\(O({n}^{2})\)的(相信大家都会写):解法1#include<bits/stdc++.h>usingnames......
  • 浅谈一下山海鲸和镝数这2款软件
    “数据可视化”无疑是当下被讨论的最多的话题之一,我也经常在各个平台看到有人提及。近日,我在翻阅知乎有关于可视化工具时经常能够看到两个名字:“山海鲸可视化”和“镝数图......
  • 浅谈自定义事件如何创建,触发和监听?
    我们知道的原生js事件,即内置事件有click,blur,mousemove等等。那如果我们想自定义一个事件呢?1、通过newEvent创建一个事件实例2、触发事件通过dispatch进行事件分发3......
  • 浅谈Go1.18版本新特性——泛型
    泛型Generics:引入了对使用参数化类型的泛型代码的新支持,达到了算法可复用的目的。两数求和,泛型函数的使用假设我们要计算两个数的和,函数可以这样子写funcAdd(a......
  • 浅谈gedit配置
    \(\text{gedit}\)是\(\text{GNOME}\)桌面的小型和轻量文本编辑器因为之前看学长博客感觉燕大的电脑似乎比较[数据删除],于是去问了\(\text{CDsidi}\)大佬\(\text{v......
  • 浅谈城市综合管廊分类及其运维管理
    陈盼安科瑞电气股份有限公司上海嘉定 20220620一、前言    综合管廊(日本称“共同沟”、中国台湾称“共同管道”),就是地下城市管道综合走廊,即在城市地下建造一个隧......
  • 浅谈弧光保护在中低压电力系统中的重要性
    陈盼安科瑞电气股份有限公司【摘要】: 中低压电力系统由于无母线保护、出线多,操作频繁、三相导体线间距离和与大地的距离比较近、易受小动物危害、设备制造质量比高压设备差......
  • 浅谈DSU on tree
    writenbyyzhon2022/20/9文中数学公式均为Latex语法前言fwyzh发现自己居然从来没写过dsuontree的题。某天在nflsoj上还是败给了dsuontree。便有此文。(时间不够......
  • 浅谈SDWAN的起源背景
    产生背景云计算技术的发展引发了IT产业的变革,互联网+带动了传统行业的转型,toB的互联网化在2014年赶超了toC。聚焦于企业市场和广域网范畴的SDWAN(SoftwareDefinedWideAr......