【无旋treap hash】匹配(2022.5.21)

上题目!

题目

1.1 题目描述

给定一个仅含小写字母的字符串 S[0..n-1],对于一个询问 (p, q, len),我们想知道它的两个子串 S[p..p+len-1]、S[q..q+len-1] 是否相同。更多地,我们希望在对串 S 完成一些操作之后还能高效地得到这个结果。 我们具体要维护以下几个操作(其中 L 为操作之前的串长):
● 1 p c:在下标 p 之前插入一个小写字母 c,0<=p<=L,p=0 意味在头插入,p=L意味在尾插入;
● 2 p: 删除 S[p],其中 0<=p<L;
● 3 p q: 将 S[p..q] 翻转为 S[q..p],例如 batta 经过 “3 1 3” 之后为 bttaa;
● 4 p q len: 表示一次询问操作,询问 S[p..p+len-1] 与 S[q..q+len-1] 是否相同,其中 0<=p<=p+len-1<L,0<=q<=q+len-1<L。

1.2 输入格式

第 1 行为两个整数 n, m,分别表示字符串的初始长度和操作次数;
第 2 行为一个长度为 n 的字符串;
接下来 m 行,每行为一个操作。

1.3 输出格式

仅一行一个整数,表示询问相同的次数。

1.4 样例输入

4 4                   
aacb                 
2 2                    // aab 
1 2 b                // aabb 
3 1 2                // abab 
4 0 2 2            // ab == ab 

1.5 样例输出

1.6 数据范围与约定

对于前20%的数据满足n,m≤1000; 
对于前70%的数据满足仅含有3、4 两种操作; 
对于100%的数据满足1≤n, m≤200000。`

第一眼看就知道是打treap或者splay的模板题由于本人过菜根本不想打又臭又长的旋转树

于是!就有了这么一个FHQ无旋treap树!(FHQ大佬%%%)

FHQ无旋treap

其树的核心在于split(分裂)和merge(合并)两个操作

先来看以val(树的大小)划分的分裂:

inline void split(int nown,int &l,int &r,int val){
if(!nown){
	l=r=0;//分到底
	return ;
}
if(size[ls[nown]]+1<=val)l=nown,split(rs[nown],rs[nown],r,val-size[ls[nown]]-1);//当前节点的size小于等于分裂值,分到左边
else r=nown,split(ls[nown],l,ls[nown],val);//同上,分到右边
update(nown);
return ;
}

注意这么一个回传值的写法

再看合并:

inline void merge(int &k,int l,int r){
if(l==0||r==0){
	k=l+r;//到底,选择其中不为0的节点回传
	return ;
}
if(num[l]>num[r])k=l,merge(rs[l],rs[l],r);//num即满足节点大根堆性质的rand()值
else k=r,merge(ls[r],l,ls[r]);
update(k); 
return ;
}

在某一个位置加入或删除节点即按照其节点位置前后分裂,加入或删除这个节点后合并

其他的操作都和普通treap大同小异

关于HASH

此题需要我们同时维护两个hash值,因为需要交换当前节点的子节点,所以要维护当前节点未交换和交换了的hash值,即:

维护以该结点为根的子树的中序字符串的哈希值(记为hash[u][0]),以及该字符串翻转后的哈希值(记为hash[u][1]),
则有:hash[u][0] = hash[child[u][0]][0]*pow[size[child[u][1]]+1]+
character[u]*pow[size[child[u][1]]]+hash[child[u][1]][0],
其中child[u][0/1]表示u的左/右儿子,size[u]表示以u为根的子树大小,pow[i]表示哈希的进制BASE的i次方。
hash[u][1]的计算同理,有:hash[u][1] = hash[child[u][0]][1]
+character[u]*pow[size[child[u][0]]]+hash[child[u][1]][1]*pow[size[child[u][0]]+1]。

交换一个节点的左右儿子时,不仅要交换它的左右儿子,也要交换它的hash[u][0]和hash[u][1]。

一些小细节

关于写tag标记是否旋转tag最好记录当前节点的子节点是否需要旋转否则计算hash会出错这件事

posted @ 2022-05-24 15:51  flywatre  阅读(195)  评论(0编辑  收藏  举报