FHQ Treap 学习笔记
FHQ 平衡树
普通平衡树
我一开始以为这玩意要比 Splay 快,但是……(似乎是常数更大
但确实是比 Splay 更好写,核心函数有两个:\(split,Merge\)
剩下的不论是求前驱后继还是排名都非常好写,只需要一直分裂合并就行
\(split\) 函数
inline void split(int now,int k,int &x,int &y)
{
if(!now) return (void)(x = y = 0);
if(val[now] <= k) x = now,split(rs(now),k,rs(now),y);
else y = now,split(ls(now),k,x,ls(now));
pushup(now);
}
\(Merge\) 函数
inline int Merge(int x,int y) // 一定要保证 x 的整棵树严格小于 y 整棵树
{
if(!x||!y) return x|y;
if(pri[x] < pri[y]){rs(x) = Merge(rs(x),y),pushup(x);return x;}
else {ls(y) = Merge(x,ls(y)),pushup(y);return y;}
}
以上就是 FHQ Treap 的两个核心函数,自上而下的分裂与合并都保证了复杂度是严格 \(O(\log n)\) 的。
文艺平衡树
一棵优秀的平衡树,不仅要实现正常平衡树的功能,而且还要支持区间操作。
FHQ Treap 就是一个可以支持快速构造区间的平衡树,可以说序列之王 Splay 能做的事,FHQ Treap都能做(除了维护 LCT
只需要将上文的split函数中的按权值分裂改为按子树大小分裂即可,代码实现过于简单,这里不再展示。
可持久化平衡树
如果 FHQ Treap 仅是支持这些操作,那我完全没必要再学一种平衡树,毕竟会 Splay 就行了。
但是 FHQ Treap 最优的一点是:支持可持久化。可以发现,FHQ Treap 不是旋转来保持平衡的,而是通过分裂与合并,这就保证了整棵树的形态不会发生变化,这也就能实现可持久化。
具体来说,FHQ Treap 主要在 \(split,Merge\) 进行可持久化,对于当前访问到的的节点,我们将其复制一遍然后在克隆的版本上进行操作。
因为 Merge 相当于split 的逆函数,所以 Merge 函数中可以不进行再复制(你手动模拟一遍会发现进行合并的节点都是在之前克隆过的节点上进行的,这样树的形态就不会乱。
贴一下两个核心函数
inline void split(int now,int k,int &x,int &y)
{
if(!now) return (void)(x = y = 0);
if(t[now].val <= k)
{
x = ++cnt,t[cnt] = t[now];
split(rs(now),k,rs(x),y);
pushup(x);
}
else
{
y = ++cnt,t[cnt] = t[now];
split(ls(now),k,x,ls(y));
pushup(y);
}
}
inline int Merge(int x,int y)
{
int aux;
if(!x||!y) return x|y;
if(t[x].pri < t[y].pri)
{
rs(x) = Merge(rs(x),y);
pushup(x);
return x;
}
else
{
ls(y) = Merge(x,ls(y));
pushup(y);
return y;
}
}
剩下的操作都按正常的来就行,没什么带问题
可持久化文艺平衡树
在可持久化文艺平衡树方面,FHQ Treap 一家独大,什么 WQLT、替罪羊都敌不过 FHQ Treap 的垄断地位。
文艺平衡树和上面的正常平衡树是一致的,只不过多了一步 pushdown
\(pushdown\) 只需要负责向下打翻转标记,打完翻转标记之后子节点发生了变化,为了防止一个点的子节点参与可持久化,但却被父节点的pushdown给影响了,所以我们直接新建两个子节点,把原来的直接丢掉就行……
剩下的同可持久化平衡树。
贴 \(pushdown,split,Merge\) 函数
inline void pushdown(int x)
{
if(t[x].tag)
{
if(ls(x))++cnt,t[cnt] = t[ls(x)],ls(x)=cnt,t[ls(x)].tag ^= 1;
if(rs(x))++cnt,t[cnt] = t[rs(x)],rs(x)=cnt,t[rs(x)].tag ^= 1;
t[x].tag ^= 1;
swap(ls(x),rs(x));
}
}
inline void split(int now,int k,int &x,int &y)
{
if(!now) return (void)(x=y=0);
pushdown(now);
if(t[ls(now)].siz + 1 <= k)
{
x = ++cnt;
t[x] = t[now];
split(rs(now),k-t[ls(now)].siz-1,rs(x),y);
pushup(x);
}
else
{
y = ++cnt;
t[y] = t[now];
split(ls(now),k,x,ls(y));
pushup(y);
}
}
inline int Merge(int x,int y)
{
if(!x||!y) return x|y;
pushdown(x);
pushdown(y);
if(t[x].pri < t[y].pri)
{
rs(x) = Merge(rs(x),y);
pushup(x);
return x;
}
else
{
ls(y) = Merge(x,ls(y));
pushup(y);
return y;
}
}
最后贴一下三个题链接
文艺平衡树维护哈希值
其实这个跟 FHQ Treap 半点关系没有,只是我用 FHQ Treap 实现的,所以贴一发
#include<bits/stdc++.h>
using namespace std;
#define ill unsigned long long
#define INF 1ll<<30
template<typename _T>
inline void read(_T &x)
{
x=0;char s=getchar();int f=1;
while(s<'0'||s>'9') {f=1;if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
x*=f;
}
const int np = 3e5 + 5;
const ill base = 29;
char c[np],bs[np],ts[np];
int n;
int siz[np],ch[2][np],pri[np],val[np],cnt,rot;
ill sum[np],p_[np];
#define ls(x) ch[0][x]
#define rs(x) ch[1][x]
inline int New(int a)
{
++cnt;
siz[cnt] = 1,pri[cnt] = rand(),val[cnt] = sum[cnt] = a;
ls(cnt) = rs(cnt) = 0;
return cnt;
}
inline void pushup(int x)
{
siz[x] = siz[ls(x)] + siz[rs(x)] + 1;
sum[x] = sum[rs(x)] + val[x] * p_[siz[rs(x)]] + sum[ls(x)] * p_[siz[rs(x)] + 1];
}
inline void split(int now,int k,int &x,int &y)
{
if(!now) return (void)(x = y =0 );
if(siz[ls(now)] + 1 <= k)
{
x = now;
split(rs(now),k - siz[ls(now)] -1,rs(x),y);
}
else
{
y = now;
split(ls(now),k,x,ls(y));
}
pushup(now);
}
inline int Merge(int x,int y)
{
if(!x || !y) return x | y;
if(pri[x] < pri[y])
{
rs(x) = Merge(rs(x),y);
pushup(x);
return x;
}
else
{
ls(y) = Merge(x,ls(y));
pushup(y);
return y;
}
}
inline ill Hash(int l,int r)
{
int x,y,z;
split(rot,r,x,y);
split(x,l-1,x,z);
ill Ans = sum[z];
rot = Merge(Merge(x,z),y);
return Ans;
}
inline void insert(int p,int a)
{
int x,y;
split(rot,p,x,y);
rot = Merge(Merge(x,New(a)),y);
}
inline void solve(int p,int a)
{
int x,y,z;
split(rot,p,x,y);
split(x,p-1,x,z);
sum[z]= val[z] = a;//我是真不知道为什么哈希值一起也要跟着改
rot = Merge(Merge(x,z),y);
}
int len(0);
inline int lcp_(int q,int p)
{
int l = 1,r = min(len - p + 1,len - q + 1),Ans(0);
while(l <= r)
{
int mid = l + r >> 1;
if(Hash(q,q + mid - 1) == Hash(p,p+mid - 1))
{
l = mid + 1;
Ans = mid;
}
else r = mid - 1;
}
return Ans;
}
signed main()
{
p_[0] = 1;
for(int i=1;i<=2e5;i++) p_[i] = p_[i - 1] * base;
scanf("%s",c + 1);
len = strlen(c + 1);
for(int i=1;i<=len;i++) insert(i - 1,c[i] - 'a' + 1);
read(n);
int Ans (0);
for(int i=1,x,y;i<=n;i++)
{
scanf("%s",bs+1);
switch(bs[1])
{
case 'Q':{
read(x);read(y);
Ans = lcp_(x,y);
printf("%d\n",Ans);
break;
}
case 'R':{
read(x);
scanf("%s",ts+1);
solve(x,ts[1] - 'a' + 1);
break;
}
case 'I':{
read(x);
len++;
scanf("%s",ts + 1);
insert(x,ts[1] - 'a' + 1);
break;
}
}
}
}
CF1340F
太难了咕了
233333
没想到我把这个题搞出来了,反过来想想还挺简单的(雾
这个题单独写了,直接挂个链接算了
CF573E Bear and Bowling
这是一道神仙 dp。
暴力 \(O(n^2)\) 很好想,设
因为状态已达最优,所以我们就没有办法继续优化了……
但是有人说根据 wqs 二分理论,这个函数 dp 在 i 固定的情况下,j 呈现一个凸函数(似乎是可以背的……
所以我们可以二分这个凸函数的顶点,在顶点左侧决策全部取 \(dp[i-1][j]\),右侧决策全部取 \(dp[i-1][j-1] + a_i*j\)。
我们可以发现执行决策二的那部分就相当于先进行了一次单点插入,然后进行区间加等差数列。这个玩意是可以用平衡树来维护的,所以直接打 FHQ Treap 就行
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF 1ll<<30
template<typename _T>
inline void read(_T &x)
{
x=0;char s=getchar();int f=1;
while(s<'0'||s>'9') {f=1;if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
x*=f;
}
const int np = 1e5 + 5;
int ch[2][np],val[np],k_[np] ,b_[np];
int pri[np],siz[np],cnt,rot;
#define ls(x) ch[0][x]
#define rs(x) ch[1][x]
inline int New(int a)
{
++cnt;
val[cnt] = a,pri[cnt] = rand();
ls(cnt) = rs(cnt) = 0;
k_[cnt] = b_[cnt] = 0;
siz[cnt] = 1;
return cnt;
}
inline void pushup(int x)
{
siz[x] = siz[ls(x)] + siz[rs(x)] + 1;
}
inline void maketag(int x,int a,int b)
{
if(x) k_[x] += a,b_[x] += b,val[x] += a * (siz[ls(x)] + 1) + b;
}
inline void pushdown(int x)
{
if(k_[x] || b_[x])
{
if(ls(x))maketag(ls(x),k_[x],b_[x]);
if(rs(x))maketag(rs(x),k_[x],(siz[ls(x)] + 1) * k_[x] + b_[x]);
k_[x] = b_[x] = 0;
}
}
inline void split(int now,int k,int &x,int &y)
{
if(!now) return (void)(x = y = 0);
pushdown(now);
if(siz[ls(now)] + 1 <= k)
{
x = now;
split(rs(now),k - siz[ls(x)] - 1,rs(x),y);
pushup(x);
}
else
{
y = now;
split(ls(now),k,x,ls(y));
pushup(y);
}
pushup(now);
}
inline int Merge(int x,int y)
{
if(!x||!y) return x|y;
pushdown(x);
pushdown(y);
if(pri[x] < pri[y])
{
rs(x) = Merge(rs(x),y);
pushup(x);
return x;
}
else
{
ls(y) = Merge(x,ls(y));
pushup(y);
return y;
}
}
inline int kth(int x,int k)
{
if(!k) return 0;
while(2333)
{
pushdown(x);
if(k <= siz[ls(x)]) x = ls(x);
else if(siz[ls(x)] + 1 == k) return x;
else k -= siz[ls(x)] + 1,x = rs(x);
}
}
inline void insert(int k,int a)
{
int x,y;
split(rot,k,x,y);
rot = Merge(Merge(x,New(a)),y);
}
inline void solve(int k,int a,int b)
{
int x,y;
split(rot,k,x,y);
maketag(y,a,b);
rot = Merge(x,y);
}
int Ans;
inline void dfs(int x)
{
if(x) pushdown(x);
if(ls(x)) dfs(ls(x));
Ans = max(Ans , val[x]);
if(rs(x)) dfs(rs(x));
}
int a[233333];
int dp[2333];
signed main()
{
int n;
read(n);
for(int i=1;i<=n;i++) read(a[i]);
insert(0,a[1]);
for(int i=2;i<=n;i++)
{
int l = 1,r = i - 1,k = i;
while(l <= r)
{
int mid = l + r >> 1;
if(val[kth(rot,mid - 1)] + a[i] * mid >= val[kth(rot,mid)])
{
r = mid - 1;
k = mid;
}
else
{
l = mid + 1;
}
}
int vl = val[kth(rot,k-1)];
insert(k-1,vl);
solve(k-1,a[i],(k-1) * a[i]);
}
dfs(rot);
cout<<Ans;
return 0;
}
\(End\)
不懂啊不懂啊
太菜了