目錄
聲明:
本系列博客是《算法競賽進階指南》+《算法競賽入門經典》+《挑戰程序設計競賽》的學習筆記,主要是因爲我三本都買了按照《算法競賽進階指南》的目錄順序學習,包含書中的少部分重要知識點、例題解題報告及我個人的學習心得和對該算法的補充拓展,僅用於學習交流和複習,無任何商業用途。博客中部分內容來源於書本和網絡(我儘量減少書中引用),由我個人整理總結(習題和代碼可全都是我自己敲噠)部分內容由我個人編寫而成,如果想要有更好的學習體驗或者希望學習到更全面的知識,請於京東搜索購買正版圖書:《算法競賽進階指南》——作者李煜東,強烈安利,好書不火系列,謝謝配合。
下方鏈接爲學習筆記目錄鏈接(中轉站)
用樹來處理整數異或問題是真的舒服!
∵ 可以處理一個字符串在一個字符串上的匹配。
又∵ 可以處理一個字符串在多個字符串上的匹配。
又∵ 自動機可以處理多個字符串在一個字符串上的匹配。
∴自動機=+
一、樹
樹(又稱字典樹,前綴樹)是一種樹形結構,是一種哈希樹的變種。典型應用是用於統計,排序和保存大量的字符串(但不僅限於字符串),所以經常被搜索引擎系統用於文本詞頻統計,用於實現字符串快速檢索的多叉樹結構。的每個節點都擁有若干個字符指針,若在插入或者檢索字符串時掃描到一個字符 ,就沿着當前節點 字符的指針,走向該指針指向的節點。
Trie樹線性(霧)處理多組字符串匹配問題,而KMP線性處理一對字符串的匹配問題。(據說學會這兩個就可以去學習自動機啦!)
是典型的用空間來換時間的做法
其中黑色的球就是字符串尾
圖源百度百科
的基本操作
這裏不想用指針(指針容易出錯,寫起來還超級麻煩)所以用數組模擬
0.初始化
注意 數組存的是結點 的 指針指向的結點編號,這裏的ch指針爲了方便被轉化成了一個整數,這點千萬不能忘
僅包含根節點,根節點中所有字符指針均爲空。
// 假設字符串由小寫字母構成
int trie[SIZE][26], tot = 1;
1.插入
注意 數組存的是結點 的 指針指向的結點編號,這裏的ch指針爲了方便被轉化成了一個整數,這點千萬不能忘
當需要插入字符串時,我們先令一個指針指向根節點,然後依次掃描中的每一個字符:
若的指向c的指針指向一個已經存在的節點,則令;
若的指向c的指針爲空,則新建一個節點,令c的指針指向,之後令;
當中的所有字符均掃描完畢時,在當前節點上標記其爲一個字符串的末尾。
// Trie的插入
void inserts(char* str){
int len = strlen(str),p = 1;//p是根結點
for(int k = 0;k < len;++k){
int ch = str[k]-'a';//相當於ch字符指針,這裏轉化成了一個整數
if(trie[p][ch] == 0)//若爲空(當前結點沒有這個字符指針),新建一個ch字符結點Q
trie[p][ch] = ++tot;//存的是結點編號
p = trie[p][ch];//p指向Q(不管有沒有這個結點,都要使得p指向Q)
}
ed[p] = true;//標記葉子結點等於true
}
2.檢索
注意 數組存的是結點 的 指針指向的結點編號,這裏的ch指針爲了方便被轉化成了一個整數,這點千萬不能忘
當需要檢索一個字符串在中是否存在時,我們另一個指針P指向根節點,然後依次掃描中的每一個字符:
如果的指向的指針爲空,說明在中不存在,結束檢索;
如果的指向的指針指向一個存在的節點 ,則令;
當中的所有字符均掃描完畢是,若當前節點 被標記爲一個字符串的末尾,說明在 中存在,否則,不存在。
空間複雜度爲,其中爲節點的個數,爲字符集的大小。
// Trie的檢索
bool searchs(char* str){
int len = strlen(str),p = 1;
for(int k = 0;k < len;++k){
p = trie[p][str[k]-'a'];//注意trie數組存的是結點p的ch指針,這裏的ch指針爲了方便被轉化爲了一個整數
if(p == 0)//一旦沒了(非洲警告!),就說明不存在
return false;
}
return ed[p];//按ed[p]的值來決定,因爲必須到達葉子結點,才爲true
}
二、樹例題:
0.luogu P2580 於是他錯誤的點名開始了(模板題)
題目鏈接:https://www.luogu.com.cn/problem/P2580
老規矩先給出一道模板題練練手(水水題 )。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
#include<bitset>
#include<limits.h>
#define ls (p<<1)
#define rs (p<<1|1)
#define mid (l+r>>1)
#define over(i,s,t) for(register int i=s;i<=t;++i)
#define lver(i,t,s) for(register int i=t;i>=s;--i)
//#define int __int128
using namespace std;
#undef mid
typedef long long ll;
typedef unsigned long long ull;
//typedef pair<int,int> PII;
const int N=1e6+7;
const ll mod=1e9+7;
const ll INF=1e15+7;
const double EPS=1e-10;
const int p=131;//13331
int n,m;
int trie[N][26],tot = 1;
int ed[N];
void inserts(char* str){
int len = strlen(str),p = 1;
for(int k = 0;k<len;++k){
int ch = str[k]-'a';
if(trie[p][ch] == 0)
trie[p][ch] = ++tot;
p = trie[p][ch];
}
ed[p]=true;
}
int val[N];
int searchs(char* str){
int len = strlen(str),p=1;
for(int k = 0;k<len;++k){
p = trie[p][str[k]-'a'];
if(p == 0)return 0;
}
if(!val[p]&&ed[p]){
val[p]=true;
return 1;
}
else if(!ed[p])return 0;
else return 2;
}
char str[N];
int main()
{
scanf("%d",&n);
over(i,1,n){
scanf("%s",str);//糟糕的 scanf 輸入體驗
inserts(str);
}
scanf("%d",&m);
over(i,1,m){
scanf("%s",str);
int ans = searchs(str);
if(ans == 0)puts("WRONG");
else if(ans == 1)puts("OK");
else puts("REPEAT");
}
return 0;
}
1.AcWing 142. 前綴統計(Trie樹模板進階題)
題目鏈接:https://www.acwing.com/problem/content/description/145/
首先對於任意一道題,任意一組數據,都要考慮重複數據的情況!
本題要求統計前綴個數,一想到字符串的前綴,我們就應該想到字典樹(前綴樹。
我們把這n個字符串插入到一顆trie樹裏,trie樹的每一個結點都存儲一個整數在ed數組裏,用來記錄該結點是多少個字符串的末尾結點。(因爲要考慮相同數據的情況,這裏不能跟普通的trie樹一樣只是結尾標記)
然後對於每一組詢問,在trie樹裏檢索輸入的字符串T,並在檢索的過程中累加路徑上每個結點的ed值,既是最後的答案。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
#include<bitset>
#include<limits.h>
#define ls (p<<1)
#define rs (p<<1|1)
#define mid (l+r>>1)
#define over(i,s,t) for(register int i=s;i<=t;++i)
#define lver(i,t,s) for(register int i=t;i>=s;--i)
//#define int __int128
using namespace std;
#undef mid
typedef long long ll;
typedef unsigned long long ull;
//typedef pair<int,int> PII;
const int N=1e6+7;
const ll mod=1e9+7;
const ll INF=1e15+7;
const double EPS=1e-10;
const int p=131;//13331
int n,m;
int trie[N][26];
int tot=1;
int ed[N];
char s[N];
void inserts(char* str){
int len = strlen(str),p = 1;
for(int k = 0;k < len;++k){
int ch = str[k]-'a';
if(trie[p][ch] == 0)
trie[p][ch] = ++tot;
p = trie[p][ch];
}
ed[p]++;
}
int searchs(char* str){
int len = strlen(str),p = 1,ans = 0;
for(int k = 0 ; k < len ; ++k){
p = trie[p][str[k]-'a'];
if(p == 0)
return ans;
ans += ed[p];//因爲本題求的是前綴,所以要累加路徑上的每個結點的ed值(可能爲0嘛)
}
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
over(i,1,n){
scanf("%s\n",s);//這個"\n"寫不寫沒有任何區別......
inserts(s);
}
over(i,1,m){
scanf("%s\n",s);
printf("%d\n",searchs(s));
}
return 0;
}
2.AcWing 143. 最大異或對
首先我們要意識到一點:任何一個整數都可以看成一個位的二進制串(數值小就在前面補0,其中int是 ~ ,第一位是符號位,所以只需要循環0 ~ 30位即可)。
我們考慮每一個二元組,且,那麼本題的目標就是想要找到的最大值,也就是說,對於每一個,我們希望找到一個,使得的值最大,並輸出這個最大值。
既然可以將這個數轉換爲字符串,那麼就可以用樹來處理。那麼我們對於每一個數,從高位往低位循環,由於要求異或和,很明顯,異或和爲1那麼值越大,所以我們對於每一位,都儘量選擇與當前的數相反的方向走(因爲是從高位往低位,所以每次貪心1最後的值最大。)我們每一次都走與當前這一位相反的位置走,也就是讓值最大,如果說沒有路可以走的話,那麼就走相同的路,這個時候值不會有任何變化。最後找到最大的這個值即可。
代碼里加了好多註釋,您應該能看懂。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
#include<bitset>
#include<limits.h>
#define ls (p<<1)
#define rs (p<<1|1)
#define mid (l+r>>1)
#define over(i,s,t) for(register int i=s;i<=t;++i)
#define lver(i,t,s) for(register int i=t;i>=s;--i)
//#define int __int128
using namespace std;
#undef mid
typedef long long ll;
typedef unsigned long long ull;
//typedef pair<int,int> PII;
const int N=1e5+7;
const ll mod=1e9+7;
const ll INF=1e15+7;
const double EPS=1e-10;
const int p=131;//13331
int n,m;
int trie[N*31][2];
int tot=1;
int ed[N];
char s[N];
void inserts(int x){
int p = 1;
lver(k,30,0){//int是2^31,有一位是符號位
int ch = x>>k&1;//去除第k位的值
if(trie[p][ch] == 0)
trie[p][ch] = ++tot;
p = trie[p][ch];
}
}
int searchs(int x){
int p = 1,ans = 0;
lver(k,30,0){
int ch = x>>k&1;
if(trie[p][ch^1]){//儘量取反,有就往反方向走
p = trie[p][ch^1];
ans |= (1<<k);//都已經往這邊走了,肯定是走與這一位相反的路,所以異或後肯定是1,所以把ans的這一位置爲1即可
}//ans |= (1<<k);//ans的第k位,0變成1,1還是1.
else p = trie[p][ch];//無奈地走相同的路,ans不變
}
return ans;
}
int a;
int ans;
int main()
{
scanf("%d",&n);
over(i,1,n){
scanf("%d",&a);
inserts(a);
ans = max(ans,searchs(a));
}
printf("%d\n",ans);
return 0;
}
3.AcWing 144. 最長異或值路徑(luogu P4551)
題目鏈接:https://www.luogu.com.cn/problem/P4551
三、自動機!
可以處理一個字符串在一個字符串上的匹配。
可以處理一個字符串在多個字符串上的匹配。
自動機可以處理多個字符串在一個字符串上的匹配。
∴自動機=+
掛一個博客就好
大佬
https://www.cnblogs.com/cjyyb/p/7196308.html
其實到也沒有那麼難
注:如果您通過本文,有(qi)用(guai)的知識增加了,請您點個贊再離開,如果不嫌棄的話,點個關注再走吧,日更博主每天在線答疑 ! 當然,也非常歡迎您能在討論區指出此文的不足處,作者會及時對文章加以修正 !如果有任何問題,歡迎評論,非常樂意爲您解答!( •̀ ω •́ )✧