【小结】平衡树
以下所有内容都是 FHQ-Treap。
P3369 【模板】普通平衡树
#include<bits/stdc++.h>
using namespace std;
mt19937 rd(114514);//种子可以随机rd(time(NULL)) 也可以
const int N=1e5+10;
int n,op,x,i,c,root;
struct Node{
int l,r,val,size,rk;//rk 随机数,val 权值,l,r 左右孩子,size 子树大小
}tr[N];
void update(int k)
{
tr[k].size=tr[tr[k].l].size+tr[tr[k].r].size+1;//左边子树+右边子树+自身
}
int add_node(int val)//加入一个新点,单独的点,先不插入树里,返回节点编号
{
c++;//c 表示节点个数
tr[c].val=val;//val 就是这个点权值
tr[c].size=1;//大小 1
tr[c].rk=rd();//随机赋值,因为要控制随机二叉搜索树
return c;
}
void split(int k,int &a,int &b,int val)//把 k 子树内的点拆分成以 a 为根的 <=val,以 b 为根的 >val
{
if (k==0)
{
a=b=0;//如果节点不存在,那么都赋值为 0
return;
}
if (tr[k].val<=val)//如果 a,第一次是直接赋值为 a,其他的就直接一个一个儿子传下去
{
a=k;
split(tr[k].r,tr[k].r,b,val);//以 r 为根,分为 r 和 b 两个,让后把 r 的这个更新 r
}
else
{
b=k;
split(tr[k].l,a,tr[k].l,val);//以 l 为根,分为 l 和 a 两个,然后把 l 更新 l
}
update(k);//更新子树大小,合并 size
}
void merge(int &k,int a,int b)//合并两个根为 a,b 的树
{
if (a==0||b==0)
{
k=a+b;//相当于 a=0 返回 b,b=0 返回 a,就是单个节点时直接返回,一定会有这样的边界,因为每次都会没掉一个点
return;
}
//a 做 b 的左儿子或 b 做 a 的右儿子
//为了 log(n),随机用 rk 的大小决定
//这样就几乎是 O(n*log(n)) 的
if (tr[a].rk<=tr[b].rk)//如果随机顺序 <=
{
k=a;//a 成为这个东西的根节点
merge(tr[a].r,tr[a].r,b);//把 r 和 b 合并,合并到 r
}
else//>
{
k=b;//b 成为这个的根节点
merge(tr[b].l,a,tr[b].l);//把 l 和 a 合并,合并到 r
}
update(k);//注意更新
}
void insert(int &k,int val)//把 val 插入树内
{
int a=0,b=0,cur=add_node(val);//cur 表示新建的点
split(k,a,b,val);//先分开
merge(a,a,cur);//然后插入到 a 的右边去
merge(k,a,b);//最后 a 并 b
}
void del(int &k,int val)//删一个 val
{
int a=0,b=0,z=0;
split(k,a,b,val);//拆
split(a,a,z,val-1);//z 就是 val
merge(z,tr[z].l,tr[z].r);//合并,取代根,删根
merge(a,a,z);//<=val 合并
merge(k,a,b);//整个合并
}
int find_num(int k,int x)//排名 x 的数
{
//分治
while (tr[tr[k].l].size+1!=x)//注意 tr[tr[k].l]]+1
{
if (tr[tr[k].l].size>=x) k=tr[k].l;//>=x 不是 >x
else
{
x-=tr[tr[k].l].size+1;//+1加自己别忘了
k=tr[k].r;//调到右儿子
}
}
return tr[k].val;//最后找到了,返回 val
}
int find_rank(int &k,int val)//val 排名
{
int a=0,b=0;
split(k,a,b,val-1);//分
int tmp=tr[a].size+1;//排名 +1
merge(k,a,b);//还原,别忘了
return tmp;//要把 tmp 存下来
}
int pre(int &k,int val)//val 前驱
{
int a=0,b=0;
split(k,a,b,val-1);//val-1
int tmp=find_num(a,tr[a].size);//<val 最后一个
merge(k,a,b);//还原
return tmp;
}
int nxt(int &k,int val)//val 后继
{
//与 pre 同理
int a=0,b=0;
split(k,a,b,val);
int tmp=find_num(b,1);
merge(k,a,b);
return tmp;
}
int main()
{
scanf("%d",&n);
//调用函数
//root 一直等于 0,但在 merge() 的时候变换
//新生成一个点有可能改变根
//root 一开始等于 0,0 也有 l,r 儿子,在弄得过程中换根保持平衡从而 O(log(n))
for (i=1;i<=n;i++)
{
scanf("%d%d",&op,&x);
if (op==1) insert(root,x);
else if (op==2) del(root,x);
else if (op==3) printf("%d\n",find_rank(root,x));
else if (op==4) printf("%d\n",find_num(root,x));
else if (op==5) printf("%d\n",pre(root,x));
else printf("%d\n",nxt(root,x));
}
return 0;
}
注:split() 有两种选择:分裂右儿子并分裂为 tr[k].r 和以上的 b,分裂左儿子同理,分裂为 tr[k].l 和以上的 a。注意 a 在前面。
merge() 也有两种选择:a 在 b 的上面,b 作为 a 的右儿子,和 tr[a].r 合并成为 rson。b 在 a 的上面,a 作为 b 的左儿子,和 tr[b].l 合并成 lson。
然后就是文艺平衡树。
文艺平衡树就是按下标分裂的平衡树,用来解决按下标操作的问题,这样直接按这个下标分裂,便于操作或找到一段区间。
split() 就是按照 \(size\) 来分。
因为原来的 \(val\) 保证了中序遍历,我可以直接用子树大小来分前 \(x\) 个。
区间翻转相当于一个子树的所有点的左右子树翻转。
所以每个点被翻转 \(m\) 次很浪费。
由于每一层都得翻转,所以打懒标记。
树的形态改变之前 pushdown()。
注意:我们分裂的并不是当前的权值,而是按大小分裂。这个东西具有单调性。而我们中间的合并纯粹是按下标合并,merge() 是随机的,树的形态会改变,但是永远保持下标的单调性。因为我是按照小,大传参的。val 表示当前的值,它改变了,但对我的 merge() 和 split() 并无影响。而下标永远保持单调性,所以最后的答案还是中序遍历。
文艺平衡树一开始顺序 merge(),就能保证下标的顺序。后面也是顺序传参,所以顺序不改变。
重要的是下标的单调性,而不是树的形态。
P3391 【模板】文艺平衡树
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m,root,c,i,l,r;
mt19937 rd(time(NULL));
struct FHQ{
int l,r,size,tag,val,rk;
}tr[N];
int add_node(int val)
{
c++;
tr[c].size=1;
tr[c].rk=rd();
tr[c].val=val;
return c;
}
void update(int k)
{
tr[k].size=tr[tr[k].l].size+tr[tr[k].r].size+1;
}
void pushdown(int k)
{
if (tr[k].tag==0) return;
swap(tr[k].l,tr[k].r);
tr[tr[k].l].tag^=1;
tr[tr[k].r].tag^=1;
tr[k].tag=0;
}
void split(int k,int &a,int &b,int x)
{
if (k==0)
{
a=b=0;
return;
}
pushdown(k);
if (tr[tr[k].l].size+1<=x)
{
a=k;
split(tr[k].r,tr[k].r,b,x-tr[tr[k].l].size-1);
}
else
{
b=k;
split(tr[k].l,a,tr[k].l,x);
}
update(k);
}
void merge(int &k,int a,int b)
{
if (a==0||b==0)
{
k=a+b;
return;
}
if (tr[a].rk<=tr[b].rk)
{
pushdown(a);
k=a;
merge(tr[a].r,tr[a].r,b);
}
else
{
pushdown(b);
k=b;
merge(tr[b].l,a,tr[b].l);
}
update(k);
}
void reverse(int &k,int l,int r)
{
int a=0,b=0,z=0;
split(k,a,b,r);
split(a,a,z,l-1);
tr[z].tag^=1;
merge(a,a,z);
merge(k,a,b);
}
void dfs(int k)
{
if (k==0) return;
pushdown(k);
dfs(tr[k].l);
printf("%d ",tr[k].val);
dfs(tr[k].r);
}
int main()
{
scanf("%d%d",&n,&m);
for (i=1;i<=n;i++)
{
add_node(i);
merge(root,root,i);
}
for (i=1;i<=m;i++)
{
scanf("%d%d",&l,&r);
reverse(root,l,r);
}
dfs(root);
return 0;
}
P3466 [POI 2008] KLO-Building blocks
枚举长度为 k 的区间 \([i-k+1,i]\),转化为求数轴上各点到一个点的距离。
容易发现,这个点选在中位数最好。
设中位数为 \(mid\),则贡献:
\(mid\times small-Small+Big-mid\times big\)。
其中 \(big\) 表示大于 \(mid\) 的数的个数,\(small\) 表示小于等于 \(mid\) 的数的个数。\(Big\) 表示大于 \(mid\) 的数的和,\(Small\) 表示小于等于 \(mid\) 的和。
套路:求区间中位数,只需要动态删点,添点,平衡树维护。
#include<bits/stdc++.h>
using namespace std;
struct FSI{
template<typename T>
FSI& operator >> (T &res){
res=0;T f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){res=res*10+ch-'0';ch=getchar();}
res*=f;
return *this;
}
}scan;
typedef long long ll;
const int N=5e5+10;
mt19937 rd(time(NULL));
int n,k,i,root,c;
ll a[N];
int pos;
ll mid,med,res;
struct FHQ{
int l,r;
ll val;
int rk,size;
ll sum;
}tr[N];
void update(int k)
{
tr[k].size=tr[tr[k].l].size+tr[tr[k].r].size+1;
tr[k].sum=tr[tr[k].l].sum+tr[tr[k].r].sum+tr[k].val;
}
void split(int k,int &a,int &b,ll val)
{
if (k==0)
{
a=b=0;
return;
}
if (tr[k].val<=val)
{
a=k;
split(tr[k].r,tr[k].r,b,val);
}
else
{
b=k;
split(tr[k].l,a,tr[k].l,val);
}
update(k);
}
void merge(int &k,int a,int b)
{
if (a==0||b==0)
{
k=a+b;
return;
}
if (tr[a].rk<=tr[b].rk)
{
k=a;
merge(tr[a].r,tr[a].r,b);
}
else
{
k=b;
merge(tr[b].l,a,tr[b].l);
}
update(k);
}
int add_cur(ll val)
{
c++;
tr[c].val=val;
tr[c].rk=rd();
tr[c].size=1;
tr[c].sum=val;
return c;
}
ll get_num(int k,int x)
{
while (tr[tr[k].l].size+1!=x)
{
if (tr[tr[k].l].size>=x) k=tr[k].l;
else
{
x-=tr[tr[k].l].size+1;
k=tr[k].r;
}
}
return tr[k].val;
}
void insert(int &k,ll val)
{
int a=0,b=0,cur=add_cur(val);
split(k,a,b,val);
merge(a,a,cur);
merge(k,a,b);
}
void del(int &k,ll val)
{
int a=0,b=0,z=0;
split(k,a,b,val);
split(a,a,z,val-1);
merge(z,tr[z].l,tr[z].r);
merge(a,a,z);
merge(k,a,b);
}
ll get_ans(ll mid)
{
int a=0,b=0;
split(root,a,b,mid);
ll small=tr[a].size,big=tr[b].size;
ll Small=tr[a].sum,Big=tr[b].sum;
merge(root,a,b);
return mid*small-Small+Big-mid*big;
}
int main()
{
scan>>n>>k;
for (i=1;i<=n;i++) scan>>a[i];
for (i=1;i<=k;i++) insert(root,a[i]);
mid=get_num(root,(k+1)/2);
res=get_ans(mid);
pos=k;
for (i=k+1;i<=n;i++)
{
del(root,a[i-k]);
insert(root,a[i]);
med=get_num(root,(k+1)/2);
if (get_ans(med)<res)
{
res=get_ans(med);
mid=med;
pos=i;
}
}
printf("%lld\n",res);
for (i=pos-k+1;i<=pos;i++) a[i]=mid;
for (i=1;i<=n;i++) printf("%lld\n",a[i]);
return 0;
}

浙公网安备 33010602011771号