【学习笔记】FHQ-Treap
一、前言
在今年 NOIP 前学习了 FHQ-Treap(一种平衡树),现在来记一记。
二、定义
Treap=tree+heap。具体的说,Treap 所维护的值满足二叉搜索树的性质,另一个变量优先级满足堆的性质。优先级一般使一个随机数,这使得树的高度保持在 \(O(\log n)\) 水平,二叉搜索树的性质使树可以维护有序的集合或序列。因为 \(O(\log n)\) 的树高,Treap 的操作时间复杂度一般都是 \(O(\log n)\) 的。
FHQ-Treap 又名无旋 Treap,它通过旋转和分裂维护堆的性质。
三、基本操作
1.合并
合并函数 merge(int p,int q)
用于合并以 \(p\) 为根的树和以 \(q\) 为根的树。需要注意的是,合并函数传入的两个参数 \(p\) 和 \(q\) 要求满足二叉搜索树的性质,因此在合并过程中我们只需维护堆的性质即可。
以小根堆为例(其实大根小根都无所谓):
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
il int merge(int p,int q){
if(!p||!q){
return p+q;
}
if(jian(p)<jian(q)){
rs(p)=merge(rs(p),q);
pushup(p);
return p;
}
else{
ls(q)=merge(p,ls(q));
pushup(q);
return q;
}
}
2.分裂
分为按值分裂和按排名分裂。以按值分裂为例,函数 split(int id,int val,int &p,int &q)
将 \(id\) 为根的树分成 \(p\) 和 \(q\) 两棵树,其中 \(p\) 中的值都小于等于 \(val\),\(q\) 中的值都大于 \(val\)。
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
il void split(int id,int val,int &p,int &q){
if(!id){
p=q=0;
return ;
}
if(val(id)<=val){
p=id;
split(rs(id),val,rs(id),q);
}
else{
q=id;
split(ls(id),val,p,ls(id));
}
pushup(id);
}
其他操作就都很简单了。
四、例题
1.普通平衡树
板子题,维护一个有序的集合。具体讲讲每个操作。
插入
将树按插入的值 \(val\) 分裂,再新建一个值为 \(val\) 的点合并进去。
删除
通过两次分裂,将所有值为 \(val\) 的点搞到一棵树中。忽略这棵树的根节点进行合并即可。
查询排名
将树按 \(val-1\) 分裂,左边的树的大小再加一就是答案。
查询第 \(k\) 小
可以按照二叉搜索树的方式递归查询。
查询前驱
按 \(val-1\) 分裂,设左边大小为 \(res\),排名为 \(res\) 的就是前驱。
查询后继
按 \(val\) 分裂,设左边大小为 \(res\),排名为 \(res+1\) 的就是后继。
Code
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
using namespace std;
namespace cplx{bool begin;}
const int maxn=1e5+5;
int n;
struct node{
int ls,rs,val,jian,sz;
node(int ls=0,int rs=0,int val=0,int jian=0,int sz=0)
:ls(ls),rs(rs),val(val),jian(jian),sz(sz){}
};
struct FHQtreap{
int rt,tot;
node tr[maxn];
il int New(int val){
tot++;
val(tot)=val;
jian(tot)=rand();
sz(tot)=1;
return tot;
}
il void pushup(int id){
sz(id)=sz(ls(id))+sz(rs(id))+1;
}
il void split(int id,int val,int &p,int &q){
if(!id){
p=q=0;
return ;
}
if(val(id)<=val){
p=id;
split(rs(id),val,rs(id),q);
}
else{
q=id;
split(ls(id),val,p,ls(id));
}
pushup(id);
}
il int merge(int p,int q){
if(!p||!q){
return p+q;
}
if(jian(p)<jian(q)){
rs(p)=merge(rs(p),q);
pushup(p);
return p;
}
else{
ls(q)=merge(p,ls(q));
pushup(q);
return q;
}
}
il void insert(int val){
int x,y;
split(rt,val,x,y);
rt=merge(merge(x,New(val)),y);
}
il void erase(int val){
int x,y,z;
split(rt,val,x,y);
split(x,val-1,x,z);
rt=merge(merge(x,merge(ls(z),rs(z))),y);
}
il int rnk(int val){
int x,y;
split(rt,val-1,x,y);
int res=sz(x)+1;
rt=merge(x,y);
return res;
}
il int kth(int id,int k){
if(sz(ls(id))==k-1){
return val(id);
}
if(sz(ls(id))>=k){
return kth(ls(id),k);
}
return kth(rs(id),k-sz(ls(id))-1);
}
il int kth(int val){
return kth(rt,val);
}
il int pre(int val){
int x,y;
split(rt,val-1,x,y);
int res=sz(x);
rt=merge(x,y);
return kth(res);
}
il int nxt(int val){
int x,y;
split(rt,val,x,y);
int res=sz(x);
rt=merge(x,y);
return kth(res+1);
}
}fhq;
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
srand(time(0));
read(n);
while(n--){
int opt,x;
read(opt)read(x)
switch(opt){
case 1:{
fhq.insert(x);
break;
}
case 2:{
fhq.erase(x);
break;
}
case 3:{
printf("%d\n",fhq.rnk(x));
break;
}
case 4:{
printf("%d\n",fhq.kth(x));
break;
}
case 5:{
printf("%d\n",fhq.pre(x));
break;
}
default:{
printf("%d\n",fhq.nxt(x));
break;
}
}
}
return 0;
}
2.文艺平衡树
维护序列。考虑翻转区间 \([l,r]\),就是将 \([l,r]\) 中的每个节点的左右儿子都交换。打上懒标记即可。
Code
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
using namespace std;
namespace cplx{bool begin;}
const int maxn=1e5+5;
int n,m,rt,tot;
struct node{
int ls,rs,val,sz,jian;
bool tag;
}tr[maxn];
il int New(int val){
tot++;
val(tot)=val;
sz(tot)=1;
jian(tot)=rand();
return tot;
}
il void pushup(int id){
sz(id)=sz(ls(id))+sz(rs(id))+1;
}
il void pushdown(int id){
if(tag(id)){
swap(ls(id),rs(id));
tag(ls(id))^=1,tag(rs(id))^=1;
tag(id)=0;
}
}
il void split(int id,int k,int &p,int &q){
if(!id){
p=q=0;
return ;
}
pushdown(id);
if(sz(ls(id))>=k){
q=id;
split(ls(id),k,p,ls(id));
}
else{
p=id;
split(rs(id),k-sz(ls(id))-1,rs(id),q);
}
pushup(id);
}
il int merge(int p,int q){
if(!p||!q){
return p+q;
}
if(jian(p)<jian(q)){
pushdown(p);
rs(p)=merge(rs(p),q);
pushup(p);
return p;
}
pushdown(q);
ls(q)=merge(p,ls(q));
pushup(q);
return q;
}
il void dfs(int id){
if(!id){
return ;
}
pushdown(id);
dfs(ls(id));
printf("%d ",val(id));
dfs(rs(id));
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
srand(time(0));
read(n)read(m);
for(int i=1;i<=n;i++){
rt=merge(rt,New(i));
}
while(m--){
int l,r;
read(l)read(r);
int x,y,z;
split(rt,r,x,y);
split(x,l-1,x,z);
tag(z)^=1;
rt=merge(merge(x,z),y);
}
dfs(rt);
return 0;
}
3.[HNOI2002] 营业额统计
显然对于每一天,答案就是这一天的值和前驱后继的差的最小值。直接维护即可。
Code
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
using namespace std;
namespace cplx{bool begin;}
const int maxn=(1<<15)+5;
const int inf=0x3f3f3f3f;
int n,rt,tot,ls[maxn],rs[maxn];
int val[maxn],sz[maxn],jian[maxn];
il int New(int x){
tot++;
val[tot]=x;
sz[tot]=1;
jian[tot]=rand();
return tot;
}
il void pushup(int id){
sz[id]=sz[ls[id]]+sz[rs[id]]+1;
}
il int merge(int p,int q){
if(!p||!q){
return p+q;
}
if(jian[p]<jian[q]){
rs[p]=merge(rs[p],q);
pushup(p);
return p;
}
ls[q]=merge(p,ls[q]);
pushup(q);
return q;
}
il void split(int id,int x,int &p,int &q){
if(!id){
p=q=0;
return ;
}
if(val[id]<=x){
p=id;
split(rs[id],x,rs[id],q);
}
else{
q=id;
split(ls[id],x,p,ls[id]);
}
pushup(id);
}
il void insert(int x){
int p,q;
split(rt,x,p,q);
rt=merge(merge(p,New(x)),q);
}
il int rnk(int x){
int p,q;
split(rt,x-1,p,q);
int res=sz[p]+1;
rt=merge(p,q);
return res;
}
il int kth(int id,int k){
if(sz[ls[id]]==k-1){
return val[id];
}
if(sz[ls[id]]>=k){
return kth(ls[id],k);
}
return kth(rs[id],k-sz[ls[id]]-1);
}
il int pre(int x){
int p,q;
split(rt,x-1,p,q);
int res=sz[p];
rt=merge(p,q);
return kth(rt,res);
}
il int nxt(int x){
int p,q;
split(rt,x-1,p,q);
int res=kth(q,1);
rt=merge(p,q);
return res;
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
read(n);
insert(-inf),insert(inf);
ll ans=0;
for(int i=1,x;i<=n;i++){
read(x);
if(i==1){
ans+=x;
}
else{
ans+=min(x-pre(x),nxt(x)-x);
}
insert(x);
}
printf("%lld",ans);
return 0;
}
4.[HNOI2004] 宠物收养场
依然是求前驱后继。要注意的是,需要记录当前平衡树内维护的是宠物还是领养者。
Code
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
using namespace std;
namespace cplx{bool begin;}
const int maxn=8e4+5,mod=1e6;
const ll inf=0x3f3f3f3f3f3f3f3f;
int n,rt,tot,ls[maxn],rs[maxn];
int sz[maxn],jian[maxn];
ll zhi[maxn];
il int New(ll val){
zhi[++tot]=val;
jian[tot]=rand();
sz[tot]=1;
return tot;
}
il void pushup(int id){
sz[id]=sz[ls[id]]+sz[rs[id]]+1;
}
il void split(int id,ll val,int &p,int &q){
if(!id){
p=q=0;
return ;
}
if(zhi[id]<=val){
p=id;
split(rs[id],val,rs[id],q);
}
else{
q=id;
split(ls[id],val,p,ls[id]);
}
pushup(id);
}
il int merge(int p,int q){
if(!p||!q){
return p+q;
}
if(jian[p]<jian[q]){
rs[p]=merge(rs[p],q);
pushup(p);
return p;
}
ls[q]=merge(p,ls[q]);
pushup(q);
return q;
}
il void insert(ll val){
int x,y;
split(rt,val,x,y);
rt=merge(merge(x,New(val)),y);
}
il void erase(ll val){
int x,y,z;
split(rt,val,x,y);
split(x,val-1,x,z);
rt=merge(merge(x,merge(ls[z],rs[z])),y);
}
il ll kth(int id,int k){
if(sz[ls[id]]==k-1){
return zhi[id];
}
if(sz[ls[id]]>=k){
return kth(ls[id],k);
}
return kth(rs[id],k-sz[ls[id]]-1);
}
il ll pre(ll val){
int x,y;
split(rt,val,x,y);
ll res=kth(x,sz[x]);
rt=merge(x,y);
return res;
}
il ll nxt(ll val){
int x,y;
split(rt,val-1,x,y);
ll res=kth(y,1);
rt=merge(x,y);
return res;
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
srand(time(0));
insert(-inf),insert(inf);
ll ans=0;
read(n);
bool flag=0;
while(n--){
int opt;
ll a;
read(opt)read(a);
if(sz[rt]==2||flag==opt){
insert(a);
flag=opt;
}
else{
ll x=pre(a),y=nxt(a);
(ans+=min(a-x,y-a))%=mod;
erase(a-x<=y-a?x:y);
}
}
printf("%lld",ans);
return 0;
}
5.「NOI 2004」郁闷的出纳员
整体修改 + 查询第 \(k\) 小,打懒标记即可。
Code
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
using namespace std;
namespace cplx{bool begin;}
const int maxn=1e5+5;
int n,m,rt,tot,ls[maxn],rs[maxn];
int zhi[maxn],jian[maxn];
int tag[maxn],sz[maxn];
il int New(int val){
zhi[++tot]=val;
jian[tot]=rand();
sz[tot]=1;
return tot;
}
il void pushup(int id){
sz[id]=sz[ls[id]]+sz[rs[id]]+1;
}
il void pushdown(int id){
if(tag[id]){
tag[ls[id]]+=tag[id];
tag[rs[id]]+=tag[id];
zhi[ls[id]]+=tag[id];
zhi[rs[id]]+=tag[id];
tag[id]=0;
}
}
il void split(int id,int val,int &p,int &q){
if(!id){
p=q=0;
return ;
}
pushdown(id);
if(zhi[id]<=val){
p=id;
split(rs[id],val,rs[id],q);
}
else{
q=id;
split(ls[id],val,p,ls[id]);
}
pushup(id);
}
il int merge(int p,int q){
if(!p||!q){
return p+q;
}
if(jian[p]<jian[q]){
pushdown(p);
rs[p]=merge(rs[p],q);
pushup(p);
return p;
}
pushdown(q);
ls[q]=merge(p,ls[q]);
pushup(q);
return q;
}
il void insert(int val){
int x,y;
split(rt,val,x,y);
rt=merge(merge(x,New(val)),y);
}
il void erase(int val){
int x,y,z;
split(rt,val,x,y);
split(x,val-1,x,z);
rt=merge(merge(x,merge(ls[z],rs[z])),y);
}
il int kth(int id,int k){
if(sz[ls[id]]==k-1){
return zhi[id];
}
pushdown(id);
if(sz[ls[id]]>=k){
return kth(ls[id],k);
}
return kth(rs[id],k-sz[ls[id]]-1);
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
srand(time(0));
read(n)read(m);
int ans=0;
while(n--){
char opt;
int x;
scanf(" %c",&opt);
read(x);
switch(opt){
case 'I':{
if(x>=m){
insert(x);
}
break;
}
case 'A':{
if(sz[rt]){
tag[rt]+=x,zhi[rt]+=x;
}
break;
}
case 'S':{
if(sz[rt]){
tag[rt]-=x,zhi[rt]-=x;
int val=kth(rt,1);
while(val<m){
erase(val),ans++;
if(sz[rt]){
val=kth(rt,1);
}
else{
break;
}
}
}
break;
}
default:{
printf("%d\n",x>sz[rt]?-1:kth(rt,sz[rt]-x+1));
break;
}
}
}
printf("%d",ans);
return 0;
}
6.[JSOI2008]火星人prefix
考虑用平衡树维护哈希。那么对于修改操作,就是一个区间加和区间乘的操作了。询问二分即可。
Code
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
using namespace std;
namespace cplx{bool begin;}
const int maxn=1e5+5;
const ull bas1=13331;
const ll bas2=13327,mod2=2.5e9+1;
int n,m,rt,tot,sz[maxn],ls[maxn],rs[maxn],jian[maxn];
ull pw1[maxn],ha1[maxn],tgj1[maxn],tgc1[maxn];
ll pw2[maxn],ha2[maxn],tgj2[maxn],tgc2[maxn];
char s[maxn],zhi[maxn];
il void pushup(int id){
sz[id]=sz[ls[id]]+sz[rs[id]]+1;
}
il void pushdown(int id){
if(tgc1[id]!=1){
if(ls[id]){
tgc1[ls[id]]*=tgc1[id];
tgj1[ls[id]]*=tgc1[id];
ha1[ls[id]]*=tgc1[id];
}
if(rs[id]){
tgc1[rs[id]]*=tgc1[id];
tgj1[rs[id]]*=tgc1[id];
ha1[rs[id]]*=tgc1[id];
}
tgc1[id]=1;
}
if(tgc2[id]!=1){
if(ls[id]){
(tgc2[ls[id]]*=tgc2[id])%=mod2;
(tgj2[ls[id]]*=tgc2[id])%=mod2;
(ha2[ls[id]]*=tgc2[id])%=mod2;
}
if(rs[id]){
(tgc2[rs[id]]*=tgc2[id])%=mod2;
(tgj2[rs[id]]*=tgc2[id])%=mod2;
(ha2[rs[id]]*=tgc2[id])%=mod2;
}
tgc2[id]=1;
}
if(tgj1[id]){
if(ls[id]){
tgj1[ls[id]]+=tgj1[id];
ha1[ls[id]]+=tgj1[id];
}
if(rs[id]){
tgj1[rs[id]]+=tgj1[id];
ha1[rs[id]]+=tgj1[id];
}
tgj1[id]=0;
}
if(tgj2[id]){
if(ls[id]){
(tgj2[ls[id]]+=tgj2[id])%=mod2;
(ha2[ls[id]]+=tgj2[id])%=mod2;
}
if(rs[id]){
(tgj2[rs[id]]+=tgj2[id])%=mod2;
(ha2[rs[id]]+=tgj2[id])%=mod2;
}
tgj2[id]=0;
}
}
il void split(int id,int k,int &p,int &q){
if(!id){
p=q=0;
return ;
}
pushdown(id);
if(sz[ls[id]]<k){
p=id;
split(rs[id],k-sz[ls[id]]-1,rs[id],q);
}
else{
q=id;
split(ls[id],k,p,ls[id]);
}
pushup(id);
}
il int merge(int p,int q){
if(!p||!q){
return p+q;
}
if(jian[p]<jian[q]){
pushdown(p);
rs[p]=merge(rs[p],q);
pushup(p);
return p;
}
pushdown(q);
ls[q]=merge(p,ls[q]);
pushup(q);
return q;
}
il int kth(int id,int k){
if(sz[ls[id]]==k-1){
return id;
}
pushdown(id);
if(sz[ls[id]]<k){
return kth(rs[id],k-sz[ls[id]]-1);
}
return kth(ls[id],k);
}
il void upd(int pos,char val){
int x,y,z;
split(rt,pos-1,x,y);
z=kth(y,1);
tgj1[y]+=pw1[pos-1]*(val-zhi[z]);
ha1[y]+=pw1[pos-1]*(val-zhi[z]);
(tgj2[y]+=(val-zhi[z]+mod2)%mod2*pw2[pos-1])%=mod2;
(ha2[y]+=(val-zhi[z]+mod2)%mod2*pw2[pos-1])%=mod2;
zhi[z]=val;
rt=merge(x,y);
}
il void insert(int pos,char val){
zhi[++tot]=val;
sz[tot]=1;
jian[tot]=rand();
tgc1[tot]=tgc2[tot]=1;
int x,y,z;
split(rt,pos,x,y);
z=kth(x,pos);
ha1[tot]=ha1[z]+pw1[pos]*val;
ha2[tot]=(ha2[z]+pw2[pos]*val)%mod2;
if(y){
tgc1[y]*=bas1;
(tgc2[y]*=bas2)%=mod2;
tgj1[y]=(tgj1[y]-ha1[z])*bas1+ha1[tot];
tgj2[y]=((tgj2[y]-ha2[z]+mod2)%mod2*bas2+ha2[tot])%mod2;
ha1[y]=(ha1[y]-ha1[z])*bas1+ha1[tot];
ha2[y]=((ha2[y]-ha2[z]+mod2)%mod2*bas2+ha2[tot])%mod2;
}
rt=merge(merge(x,tot),y);
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
srand(time(0));
pw1[0]=pw2[0]=1;
for(int i=1;i<=1e5;i++){
pw1[i]=pw1[i-1]*bas1;
pw2[i]=pw2[i-1]*bas2%mod2;
}
scanf(" %s",s+1);
n=strlen(s+1);
rt=tot=1;
sz[rt]=1,jian[rt]=rand();
tgc1[rt]=tgc2[rt]=1;
for(int i=1;i<=n;i++){
insert(i,s[i]);
}
read(m);
while(m--){
char opt;
scanf(" %c",&opt);
switch(opt){
case 'Q':{
int x,y,p,q;
read(x)read(y);
if(x>y){
swap(x,y);
}
p=kth(rt,x);
q=kth(rt,y);
ull hx1=ha1[p],hy1=ha1[q];
ll hx2=ha2[p],hy2=ha2[q];
int l=0,r=sz[rt]-y;
while(l<r){
int mid=(l+r+1)>>1;
p=kth(rt,x+mid);
q=kth(rt,y+mid);
if((ha1[p]-hx1)*pw1[y-x]==ha1[q]-hy1&&(ha2[p]-hx2+mod2)%mod2*pw2[y-x]%mod2==(ha2[q]-hy2+mod2)%mod2){
l=mid;
}
else{
r=mid-1;
}
}
printf("%d\n",l);
break;
}
case 'R':{
int x;
char val;
read(x);
scanf(" %c",&val);
upd(x+1,val);
break;
}
default:{
int x;
char val;
read(x);
scanf(" %c",&val);
insert(x+1,val);
break;
}
}
}
return 0;
}
7.[Tjoi2013]最长上升子序列
考虑 DP,设 \(dp_i\) 表示以 \(i\) 结尾的最长上升子序列长度。有方程:
因为是从小到大插入,我们就只用维护 \(j<i\) 的限制就行了。用平衡树维护序列即可。
Code
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
using namespace std;
namespace cplx{bool begin;}
const int maxn=1e5+5;
int n,rt,tot,sz[maxn],zhi[maxn],mx[maxn],jian[maxn],ls[maxn],rs[maxn];
il void pushup(int id){
mx[id]=max({zhi[id],mx[ls[id]],mx[rs[id]]});
sz[id]=sz[ls[id]]+sz[rs[id]]+1;
}
il void split(int id,int k,int &p,int &q){
if(!id){
p=q=0;
return ;
}
if(sz[ls[id]]<k){
p=id;
split(rs[id],k-sz[ls[id]]-1,rs[id],q);
}
else{
q=id;
split(ls[id],k,p,ls[id]);
}
pushup(id);
}
il int merge(int p,int q){
if(!p||!q){
return p+q;
}
if(jian[p]<jian[q]){
rs[p]=merge(rs[p],q);
pushup(p);
return p;
}
ls[q]=merge(p,ls[q]);
pushup(q);
return q;
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
read(n);
srand(time(0));
for(int i=1,p,x,y;i<=n;i++){
read(p);
split(rt,p,x,y);
sz[++tot]=1,jian[tot]=rand();
mx[tot]=zhi[tot]=mx[x]+1;
rt=merge(merge(x,tot),y);
printf("%d\n",mx[rt]);
}
return 0;
}
8.二逼平衡树
线段树套平衡树。
在每个线段树的节点上维护一个平衡树,维护这个区间中的值。逐一分析每个操作。
查询排名
考虑排名的本质,就是比 \(val\) 小的值的数量加一。在线段树上将区间拆分成 \(O(\log n)\) 个,再在每个区间中查询累加答案即可。
查询第 \(k\) 小
这不好直接查,二分即可。
单点修改
在线段树上每个相关的节点修改即可。
查询前驱
前驱就是比 \(val\) 小的值中最大的值。在每个区间内查询并取 \(\max\) 即可。
查询后继
类似前驱。
Code
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=5e4+5,inf=0x3f3f3f3f;
int n,m,a[maxn],rt[maxn<<2];
namespace fhq{
int tot,zhi[maxn*20],sz[maxn*20];
int ls[maxn*20],rs[maxn*20],jian[maxn*20];
int ljt[maxn*20],top;
il int New(int val){
int p=top?ljt[top--]:++tot;
zhi[p]=val;
jian[p]=rand();
sz[p]=1;
ls[p]=rs[p]=0;
return p;
}
il void pushup(int id){
sz[id]=sz[ls[id]]+sz[rs[id]]+1;
}
il void split(int id,int val,int &p,int &q){
if(!id){
p=q=0;
return ;
}
if(zhi[id]<=val){
p=id;
split(rs[id],val,rs[id],q);
}
else{
q=id;
split(ls[id],val,p,ls[id]);
}
pushup(id);
}
il int merge(int p,int q){
if(!p||!q){
return p+q;
}
if(jian[p]<jian[q]){
rs[p]=merge(rs[p],q);
pushup(p);
return p;
}
ls[q]=merge(p,ls[q]);
pushup(q);
return q;
}
il int rnk(int &id,int val){
int x,y;
split(id,val-1,x,y);
int res=sz[x]+1;
id=merge(x,y);
return res;
}
il void insert(int &id,int val){
int x,y;
split(id,val,x,y);
id=merge(merge(x,New(val)),y);
}
il void del(int &id,int val){
int x,y,z;
split(id,val,x,y);
split(x,val-1,x,z);
ljt[++top]=z;
id=merge(merge(x,merge(ls[z],rs[z])),y);
}
il int kth(int id,int k){
if(sz[ls[id]]==k-1){
return zhi[id];
}
if(sz[ls[id]]>=k){
return kth(ls[id],k);
}
return kth(rs[id],k-sz[ls[id]]-1);
}
il int qanq(int &id,int val){
int x,y;
split(id,val-1,x,y);
int res=sz[x]?kth(x,sz[x]):-inf;
id=merge(x,y);
return res;
}
il int houj(int &id,int val){
int x,y;
split(id,val,x,y);
int res=sz[y]?kth(y,1):inf;
id=merge(x,y);
return res;
}
}
il void build(int id,int l,int r){
for(int i=l;i<=r;i++){
fhq::insert(rt[id],a[i]);
}
if(l==r){
return ;
}
int mid=(l+r)>>1;
build(lid,l,mid);
build(rid,mid+1,r);
}
il int qrnk(int id,int L,int R,int l,int r,int val){
if(L>=l&&R<=r){
return fhq::rnk(rt[id],val);
}
int mid=(L+R)>>1,res=0;
if(l<=mid){
res+=qrnk(lid,L,mid,l,r,val)-1;
}
if(r>mid){
res+=qrnk(rid,mid+1,R,l,r,val)-1;
}
return res+1;
}
il void upd(int id,int l,int r,int pos,int val){
fhq::del(rt[id],a[pos]);
fhq::insert(rt[id],val);
if(l==r){
return ;
}
int mid=(l+r)>>1;
if(pos<=mid){
upd(lid,l,mid,pos,val);
}
else{
upd(rid,mid+1,r,pos,val);
}
}
il int qqanq(int id,int L,int R,int l,int r,int val){
if(L>=l&&R<=r){
return fhq::qanq(rt[id],val);
}
int mid=(L+R)>>1,res=-inf;
if(l<=mid){
res=max(res,qqanq(lid,L,mid,l,r,val));
}
if(r>mid){
res=max(res,qqanq(rid,mid+1,R,l,r,val));
}
return res;
}
il int qhouj(int id,int L,int R,int l,int r,int val){
if(L>=l&&R<=r){
return fhq::houj(rt[id],val);
}
int mid=(L+R)>>1,res=inf;
if(l<=mid){
res=min(res,qhouj(lid,L,mid,l,r,val));
}
if(r>mid){
res=min(res,qhouj(rid,mid+1,R,l,r,val));
}
return res;
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
srand(time(0));
read(n)read(m);
for(int i=1;i<=n;i++){
read(a[i]);
}
build(1,1,n);
while(m--){
int opt;
read(opt);
switch(opt){
case 1:{
int l,r,val;
read(l)read(r)read(val);
printf("%d\n",qrnk(1,1,n,l,r,val));
break;
}
case 2:{
int l,r,k;
read(l)read(r)read(k);
int L=-inf,R=inf;
while(L<R){
int mid=(L+R+1)>>1;
if(qrnk(1,1,n,l,r,mid)<=k){
L=mid;
}
else{
R=mid-1;
}
}
printf("%d\n",L);
break;
}
case 3:{
int pos,val;
read(pos)read(val);
upd(1,1,n,pos,val);
a[pos]=val;
break;
}
case 4:{
int l,r,val;
read(l)read(r)read(val);
printf("%d\n",qqanq(1,1,n,l,r,val));
break;
}
default:{
int l,r,val;
read(l)read(r)read(val);
printf("%d\n",qhouj(1,1,n,l,r,val));
break;
}
}
}
return 0;
}
}
int main(){return asbt::main();}
9.星系探索
考虑给树建一个欧拉序,对于点 \(u\),在进入 \(u\) 的子树时将 \(u\) 加入序列,记位置为 \(dfn_u\);出 \(u\) 的子树是再将 \(u\) 加入序列,记位置为 \(low_u\)。
如果在 \(dfn_u\) 记录 \(u\) 的权值,在 \(low_u\) 记录 \(u\) 的权值的负值,那么对于节点 \(u\),欧拉序上 \([1,dfn_u]\) 的和就是根到 \(u\) 的权值之和。
子树加是简单的,考虑子树移动。对应到区间上,其实就是区间平移。那么看起来似乎就很简单了。问题在于,平移后每个节点的排名可能会改变,要再按排名分裂会出问题。考虑对每个节点,再记录它的父亲节点 \(fa\)。那么一个节点当前的排名就可以跳 \(fa\) 来求了。
Code
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=2e5+5;
int n,m,dfn[maxn],low[maxn];
int rt,tot,ls[maxn],rs[maxn];
int fa[maxn],zhi[maxn],sum[maxn];
int sz[maxn],jian[maxn],a[maxn];
int col[maxn],tcl[maxn],tag[maxn];
vector<int> e[maxn];
il int New(int x,int y){
jian[++tot]=rand();
sum[tot]=zhi[tot]=x;
tcl[tot]=col[tot]=y;
sz[tot]=1,fa[tot]=0;
return tot;
}
il void pushup(int id){
sum[id]=sum[ls[id]]+sum[rs[id]]+zhi[id];
tcl[id]=tcl[ls[id]]+tcl[rs[id]]+col[id];
sz[id]=sz[ls[id]]+sz[rs[id]]+1;
}
il void pushdown(int id){
if(tag[id]){
tag[ls[id]]+=tag[id];
tag[rs[id]]+=tag[id];
zhi[ls[id]]+=col[ls[id]]*tag[id];
zhi[rs[id]]+=col[rs[id]]*tag[id];
sum[ls[id]]+=tag[id]*tcl[ls[id]];
sum[rs[id]]+=tag[id]*tcl[rs[id]];
tag[id]=0;
}
}
il void split(int id,int k,int &p,int &q){
if(!id){
p=q=0;
return ;
}
pushdown(id);
if(sz[ls[id]]<k){
p=id;
split(rs[id],k-sz[ls[id]]-1,rs[id],q);
fa[rs[id]]=id;
}
else{
q=id;
split(ls[id],k,p,ls[id]);
fa[ls[id]]=id;
}
pushup(id);
}
il int merge(int p,int q){
if(!p||!q){
return p+q;
}
if(jian[p]<jian[q]){
pushdown(p);
rs[p]=merge(rs[p],q);
fa[rs[p]]=p;
pushup(p);
return p;
}
pushdown(q);
ls[q]=merge(p,ls[q]);
fa[ls[q]]=q;
pushup(q);
return q;
}
il void dfs(int u,int fth){
rt=merge(rt,dfn[u]=New(a[u],1));
fa[rt]=0;
for(int v:e[u]){
dfs(v,u);
}
rt=merge(rt,low[u]=New(-a[u],-1));
fa[rt]=0;
}
il int rnk(int id){
int res=sz[ls[id]]+1;
while(fa[id]){
if(id==rs[fa[id]]){
res+=sz[ls[fa[id]]]+1;
}
id=fa[id];
}
return res;
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
signed main(){
read(n);
for(int i=2,x;i<=n;i++){
read(x);
e[x].pb(i);
}
for(int i=1;i<=n;i++){
read(a[i]);
}
dfs(1,0);
read(m);
while(m--){
char opt;
scanf(" %c",&opt);
switch(opt){
case 'Q':{
int u,x,y;
read(u);
split(rt,rnk(dfn[u]),x,y);
fa[x]=fa[y]=0;
printf("%lld\n",sum[x]);
rt=merge(x,y);
fa[rt]=0;
break;
}
case 'C':{
int u,v;
read(u)read(v);
int l=rnk(dfn[u]),r=rnk(low[u]);
int tv=rnk(dfn[v]);
int x,y,z;
split(rt,r,x,y);
fa[x]=fa[y]=0;
split(x,l-1,x,z);
fa[x]=fa[z]=0;
if(tv<l){
int w;
split(x,tv,x,w);
fa[x]=fa[w]=0;
rt=merge(merge(x,z),merge(w,y));
fa[rt]=0;
}
else{
int w;
split(y,tv-r,w,y);
rt=merge(merge(x,w),merge(z,y));
fa[rt]=0;
}
break;
}
default:{
int u,val;
read(u)read(val);
int l=rnk(dfn[u]),r=rnk(low[u]);
int x,y,z;
split(rt,r,x,y);
split(x,l-1,x,z);
tag[z]+=val,zhi[z]+=col[z]*val;
sum[z]+=val*tcl[z];
rt=merge(merge(x,z),y);
break;
}
}
}
return 0;
}
}
signed main(){return asbt::main();}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库