1. 程式人生 > 其它 >【無旋treap hash】匹配(2022.5.21)

【無旋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會出錯這件事