书接去年,今天作业不想写了,滚过来写总结。顺便保留我刚略微学会的串串。
声明:作者由于水平不高,所以有些定理不能严谨证明,所以若是初学者请移步别处。
1.Trie树
定义
Trie树又叫字典树,是非常显然的一种字符串数据结构。
具体来说,我们如果手上有很多字符串,我们可以通过建一个Trie树来做一些简单的操作。
其构建过程可以看一下图,非常显然,本质就是把字符串的相同部分压缩一下。
比如我们对
aa
aba
ba
caaa
cab
cba
cc
建一个Trie就长这样。
构建
图来自OI wiki

然后构建这块也挺简单的
代码
cpp
int len,idx,cnt[N],tr[N][70];
ll getid(char c){
if(c>='A'&&c<='Z') return c-'A';
else if(c>='a'&&c<='z') return c-'a'+26;
else return c-'0'+52;
}
void add(string s){
len=s.size();
s=' '+s;
int p=0;
for(int i=1;i<=len;i++){
int id=getid(s[i]);
if(!tr[p][id]) tr[p][id]=++idx;
p=tr[p][id];
cnt[p]++;
}
}
ll ask(string s){
len=s.size();
s=' '+s;
int p=0;
for(int i=1;i<=len;i++){
int id=getid(s[i]);
if(!tr[p][id]) return 0;
p=tr[p][id];
}
return cnt[p];
}
空间这块,最坏情况每个字符串的每个字符都要新开一个节点,所以空间开即可。
上一道例题
AT_arc219_a ARC219A Similarity
AT_arc219_a ARC219A Similarity

正难则反

这个比较困难
所以就把S串01翻转,那么就是找出一个串串使得不能与任何一个S串相同。
那你给这些反串建出Trie后,找一个还没有被经过的节点即可。
例题结束
与异或相关
其实我们的Trie树不只能在字符串上用
因为在我们眼里数是有二进制的,二进制我们就可以想到01串。
我们就可以把数字当做01串建Trie树
当我们遇到一些神秘有关异或的题目时,
我们一想Trie树,二想线性基
比如来道题,给定你一个序列和x值,让你从这个序列中选一个元素,使之其与x的异或值最大。
考虑异或的性质,二进制位上不同为一,相同为0。
所以我们对序列建Trie树,把给定的x二进制分解,从高往低位的贪心的尽可能与x当前位不同。就行了。
来一道有意思的应用
P5283 十二省联考 2019 异或粽子
2.AC自动机
定义
AC自动机,由两部分构成Trie树和fail树。
AC自动机的本质上就是建出Trie树后连fail边。
这里我们给出fail边的定义。
在Trie树上,每一个节点表示一个字符串。
fail边指向这个节点所代表的字符串 最长的后缀 所代表的节点。
图例
example:
图来自OI wiki

比如这个Trie树
给他建fail边就应该是这样的

由于根节点代表的是空串,空串是任何字符串的后缀。
所以当你实在在Trie树上找不到后缀时,就直接无脑连根结点就可以啦。
然后AC自动机的功能这块,可以通过拿一个字符串在上面跑得到在自动机上他的所有子串,不管通过Trie边还是fail边。
代码
cpp
int n,m,len,idx=1;
string s;
struct trie{
int son[29],fail;
int flag;
}t[N];
struct AC_automation{
void build(string s){
len=s.size(); s=' '+s;
int p=0;
for(int i=1;i<=len;i++){
int v=s[i]-'a'+1;
if(!t[p].son[v]) t[p].son[v]=++idx;
p=t[p].son[v];
}
t[p].flag++;
}
void getfail(){
queue<int> q;
for(int i=1;i<=26;i++) if(t[0].son[i]) q.push(t[0].son[i]);
while(!q.empty()){
int p=q.front();
q.pop();
for(int i=1;i<=26;i++){
int v=t[p].son[i];
int faf=t[p].fail;
if(!v){
t[p].son[i]=t[faf].son[i];
continue;
}
t[v].fail=t[faf].son[i];
q.push(v);
}
}
}
ll ask(string s){
len=s.size(); s=' '+s;
int p=0; ll ans=0;
for(int i=1;i<=len;i++){
int v=s[i]-'a'+1;
int k=t[p].son[v];
while(k>1&&t[k].flag!=-1){
ans+=t[k].flag,t[k].flag=-1;
k=t[k].fail;
}
p=t[p].son[v];
}
return ans;
}
}AC;
P9613 CERC2019 K==S

大意就是构造一个长度为N的串串,让你是这个串串不包含给出的禁用短语作为子串,求方案数。
多模匹配想到建AC自动机并建图,由于不能含某些子串,所以不能走到这些子串的节点以及能通过跳fail边到这些子串的地方。
发现N很大,直接DP不行。但是状态转移固定,所以使用矩阵乘法优化DP就好了。
3.后缀数组
其实有点跑题了,因为后缀数组是线性的,但是后缀数组比较困难所以就写在这里了
定义

性质


图文来自洛谷

代码
cpp
string s;
int sa[N],c[N],x[N],y[N],height[N],rk[N];
namespace SA{
void getsa(){
for(int i=1;i<=n;i++)++c[x[i]=s[i]];
for(int i=2;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i;i--)sa[c[x[i]]--]=i;
for(int k=1;k<=n;k*=2){
int num=0;
for(int i=n-k+1;i<=n;i++)y[++num]=i;
for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;
for(int i=1;i<=m;i++)c[i]=0;
for(int i=1;i<=n;i++)++c[x[i]];
for(int i=2;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
swap(x,y);
x[sa[1]]=1;
num=1;
for(int i=2;i<=n;i++){
x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
}
if(num==n)break;
m=num;
}
}
void getheight(){
for(int i=1;i<=n;i++) rk[sa[i]]=i;
int k=0;
for(int i=1;i<=n;i++){
if(rk[i]==1) continue;
if(k) k--;
int j=sa[rk[i]-1];
while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) k++;
height[rk[i]]=k;
}
}
}
using namespace SA;