字典樹Trie學習筆記

一個簡單的問題

問題:有nn個由小寫字母組成的字符串(n105n\le 10^5,字符串長度L20L\le 20)。有QQ組詢問(Q105Q\le10^5),每次給出一個字符串,你需要回答這個字符串在給出的nn個字符串中是否出現。

方法一:暴力,每個詢問和前面大莉比較,時間複雜度O(QnL)O(QnL)
方法二:把nn個字符串存入map中,每個詢問在map中查詢。不太清楚map複雜度怎麼算,目測總時間複雜度O(Lnlogn+LQlogn)O(Lnlogn+LQlogn)
方法三:蛤希,時間複雜度O(Ln+LQ)O(Ln+LQ),但可能會有蛤希衝突。
方法四:Trie。

Trie樹基本內容

建立和查詢方法

在Trie樹中,我們用邊來表示每個字符,點表示字符串結束的位置。
讓每個點所對應的字符串爲根節點到這個點的路徑所對應的字符按順序連接得到的字符串。
舉個例子:現在有四個字符串ypy,ygy,yxc,mhy。那麼珂以根據規則建立Trie樹:
在這裏插入圖片描述
建立了trie樹,便珂以在trie樹上維護信息。例題中,珂以在字符串出現的位置打上標記:
在這裏插入圖片描述
每次詢問時,按照字符串訪問每個字符所對的邊,看最後到達的節點是否有標記即珂。

毒瘤代碼實現

實現時使用動態開點。
變量定義&插入代碼:

#define re register int
int tot=1;
struct node {
	int ch[27];
	bool flag;
} w[Size*21];
void insert(char *str) {
	int len=strlen(str),rt=1;
	for(re i=0; i<len; i++) {
		int p=str[i]-'a';
		rt=w[rt].ch[p]?w[rt].ch[p]:w[rt].ch[p]=++tot;	//實測insert中用三目運算符比if快10%左右
	}
	w[rt].flag=true;
}

詢問代碼:

bool query(char *str) {
	int len=strlen(str),rt=1;
	for(re i=0; i<len; i++) {
		rt=w[rt].ch[str[i]-'a'];
		if(!rt)	return false;	//rt=0說明沒有這樣的字符串,直接剪枝
	}
	return w[rt].flag;
}

Trie題目

例題一

UVA11362 Phone List & SP4033 Phone List & poj3630 Phone List
給定 nn 個長度不超過 1010 的數字串,問其中是否存在兩個數字串 S,TS,T,使得 SSTT 的前綴。

把所有數字串存入Trie中,Trie每個節點保存子樹內有多少個節點對應的字符串存在(即每個節點保存有多少個字符串的前綴爲這個節點對應字符串的),記爲sumsum
每個節點再保存是否存在這個節點對應的字符串,記爲flagflag
那麼若這個節點的字符串是其他字符串的前綴,當且僅當flagflag &&\&\& sum>1sum>1
所以dfs一遍即珂。
代碼略。

例題二

洛谷P5768 [CQOI2016]路由表 LOJ #2046 [CQOI2016]路由表
題目大意:路由表中珂以存儲IP地址,稱爲表項。每個表項有一個掩碼長度。
對於一個給定的IP(目的地址),路由表會將這個IP與所有表項比較,比較時只會比較前掩碼長度位。(掩碼長度指的是這個表項的掩碼長度)
在所有能匹配的掩碼中,路由表會選出一個掩碼長度最長的IP,稱爲表項選擇。
起初路由表是空的。要求資瓷兩種操作:
1.往路由表中插入一個掩碼長度爲ll的表項
2.給出一個目的地址和兩個整數a,ba,b,輸出這個IP在第aa次和第bb次插入表項之間,表項選擇變了多少次

建立Trie與例題一同理(把IP的前ll位插入Trie中)。
查詢方式:
考慮表項變化的情況,發現表項變化只有可能是掩碼長度大於之前的掩碼長度,且出現時間比之前的IP晚。
如果出現時間比前面的IP早且掩碼長度比前面的IP掩碼長度長,則說明前面的IP不會稱爲表項選擇。
因此根據給出的目的地址在Trie中遞歸,用單調棧記錄一個單調遞增的時間序列,最後其中時間>=l>=l<=r<=r的就是答案。

毒瘤代碼
#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<math.h>
#define re register int
using namespace std;
typedef long long ll;
int read() {
	re x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=10*x+ch-'0';
		ch=getchar();
	}
	return x*f;
}
inline void write(const int x) {
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
inline char GetChar() {
	char ch=getchar();
	while(ch!='A' && ch!='Q')	ch=getchar();
	return ch;
}
const int Size=1000005;
int tot=1;
struct node {
	int ch[2];
	int tim;
} w[Size*35];
void insert(unsigned x,int l,int t) {
	int rt=1;
	for(re i=31,j=1; j<=l; i--,j++) {
		int p=(x>>i)&1;
//		if(!w[rt].ch[p])	w[rt].ch[p]=++tot;
		rt=w[rt].ch[p]?w[rt].ch[p]:w[rt].ch[p]=++tot;
	}
	w[rt].tim=t;
}
int stk[Size];
int query(unsigned x,int l,int r) {
	int rt=1,top=0;
	for(re i=31; i>=0; i--) {
		int p=(x>>i)&1;
		rt=w[rt].ch[p];
		if(!rt)	break;
		if(w[rt].tim) {
			while(top>0 && w[rt].tim<stk[top]) {
				top--;
			}
			stk[++top]=w[rt].tim;
		}
	}
	int ans=0;
	for(re i=1; i<=top; i++) {
		if(stk[i]>=l && stk[i]<=r) {
			ans++;
		}
	}
	return ans;
}
int main() {
	int m=read();
	int l,a,b,num=0;
	while(m--) {
		char ch=GetChar();
		unsigned x1=read();
		unsigned x2=read();
		unsigned x3=read();
		unsigned x4=read();
		unsigned ip=x4|(x3<<8)|(x2<<16)|(x1<<24);
		if(ch=='A') {
			l=read();
			insert(ip,l,++num);
		} else {
			a=read();
			b=read();
			write(query(ip,a,b));
			putchar(10);
		}
	}
	return 0;
}

例題三

LOJ #10050 The XOR Largest Pair
給定nn個整數a1,a2,...,ana_1,a_2,...,a_n,取出兩個數進行異或運算,得到的結果最大是多少?

掃一遍,每次用aia_i在Trie樹中的異或最大值更新答案,再把aia_i插入到Trie樹中。
aia_i在Trie樹中異或最大值求法:讓另外一個節點向下搜索,儘量與aia_i反方向走。顯然這樣異或是最大的。
代碼略。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章