【集训】ZR集训——字符串
8.5
异或马拉车 P3501
8.6
ACAM
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;int n;
int trans[N][26];
int cnt=0;
int fail[N];
int to[N];
int insert(string s){
int now=0;
for(auto v:s){
if(!trans[now][v-'a']) trans[now][v-'a']=++cnt;
now=trans[now][v-'a'];
}
return now;
}
vector<int> mp[N];
void Fail(){
queue<int> q;
for(int i=0;i<=25;i++){
if(trans[0][i]) q.push(trans[0][i]);
}
while(q.size()){
int u=q.front();q.pop();
//cout<<u<<" ";
for(int i=0;i<=25;i++){
if(trans[u][i]){
fail[trans[u][i]]=trans[fail[u]][i];
q.push(trans[u][i]);
}
else trans[u][i]=trans[fail[u]][i];//变成了trie图
}
}
for(int i=1;i<=cnt;i++) mp[fail[i]].push_back(i);
}
int num[N];
void dfs(int u){
for(auto v:mp[u]){
dfs(v);
num[u]+=num[v];
}
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
string s;cin>>s;
to[i]=insert(s);
}
Fail();
string s;cin>>s;
int now=0;
for(auto v:s){
now=trans[now][v-'a'];
num[now]++;
}
dfs(0);//暴力跳fail,转差分统计
for(int i=1;i<=n;i++) cout<<num[to[i]]<<"\n";
return 0;
}
理解
AC自动机三类指针:trie指针,fail指针,末指针
AC自动机的核心是fail指针建立,后面的query是在其上面做一些其他操作
优化:暴力跳fail,转为拓扑差分求和,这个优化是必须的
AC自动机复杂度分析(trie跳跃),trie树数组大小分析(节点数*字符集大小)
节点有可能要提前继承fail上祖宗的信息(P2444)
trie和fail可以拆开,Trie 树上的祖先节点代表前缀;fail 指针指向的节点代表最长后缀,沿着 fail 链跳能得到所有后缀。
trie树trie图分离,一般用于被迫需要Dfs遍历trie,且与自动机无关(P2414)
应用
AC自动机上dp(P3041):不可以Dfs(因为trie图有环),可以用类似分层图思想刷表转移
反匹配(P2444):trie图找环
PAM
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+10;
int trans[N][26];//trie
int fail[N];//fail
int len[N];//长度
int num[N];//答案
int pre=0;//上一个点
int cnt=1;//总数,一定从1开始!!
string s;
int Fail(int x,int tp){
while(tp-len[x]-1<0||s[tp-len[x]-1]!=s[tp]) x=fail[x];
return x;
}
void insert(char c,int tp){
int fa=Fail(pre,tp);
if(!trans[fa][c-'a']){
++cnt;
//更新节点信息
len[cnt]=len[fa]+2;
fail[cnt]=trans[Fail(fail[fa],tp)][c-'a'];//Fail(fail[fa],tp)不是fail[fa]
num[cnt]=num[fail[cnt]]+1;
trans[fa][c-'a']=cnt;//更新放后面,防止自循环
}
pre=trans[fa][c-'a'];
}
int main(){
cin>>s;
len[0]=0;len[1]=-1;
fail[0]=1;fail[1]=1;
int ans=0;
for(int i=0;i<s.size();i++){
s[i]=(s[i]-97+ans)%26+97;
insert(s[i],i);
ans=num[pre];
cout<<ans<<" ";
}
return 0;
}
最小回文划分(❌️)
Series Link(❌️)
border理论
-
如果一个长度为 \(n\) 的字符串 \(S\) 有一个长度为 \(L\) 的Border,那么 \(n - L\) 就是 \(S\) 的一个周期。
-
\(border(S)=border(maxbd(S))+maxbd(S)\) ,表集合
-
对于每个 \(u\),连边 \(u->π(u)\),就成了 border 树(或者叫失配树). \(π(u)\) 是 \(u\) 的最长border
上面的东西显而易见
弱周期定理
一个字符串 \(S\),若其有长度为 \(p\) 和 \(q\) 的周期,且 \(p+q≤|S|\),则 \(S\) 有长度为 \(gcd(p,q)\) 的周期 .
根据周期定义和更相减损术证明
强周期定理
一个字符串 \(S\),若其有长度为 \(p\) 和 \(q\) 的周期,且 \(p+q−gcd(p,q)≤|S|\),则 \(S\) 有长度为 \(gcd(p,q)\) 的周期 .
border理论核心推论
一个字符串的所有Border在长度上可以被划分为 \(O(log n)\) 个等差数列
准确来说,将所有 Border 按其二进制最高位进行分段,每一段内的 Border 都构成一个等差数列
8.7
两个都要学
SA
理解
height数组是SA的精髓,也是相比于SAM的优势所在
sa,rk,h,height
参看oi-wiki
基数排序是稳定排序,故而正确
rk是求sa的中间产物
本质不同子串计数:减去sum(height[i])
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
char c[N];
int sa[N],rk[N*2];
int tmp[N];
int tot;
int up=131;
int n;
int bin[N];
void sortsa(){//按照第一关键字排序
memset(bin,0,sizeof bin);
for(int i=1;i<=n;i++) bin[rk[i]]++;
for(int i=1;i<=up;i++) bin[i]+=bin[i-1];
for(int i=n;i>0;i--) sa[bin[rk[tmp[i]]]--]=tmp[i];//保证稳定排序
}
int main(){
string s;cin>>s;
n=0;
for(auto v:s) c[++n]=v;
for(int i=1;i<=n;i++){
rk[i]=c[i];
tmp[i]=i;
}
sortsa();
//rk是离散化后的关键字
for(int i=1;i<n;i<<=1){
tot=0;
//tmp作sa(第二关键字排序)
for(int j=n-i+1;j<=n;j++) tmp[++tot]=j;
for(int j=1;j<=n;j++){
if(sa[j]-i>0) tmp[++tot]=sa[j]-i;
}
sortsa();
//以上可直接替换为sort
//tmp作rk(重新分配)
tmp[sa[1]]=up=1;
for(int j=2;j<=n;j++){
if(rk[sa[j]]!=rk[sa[j-1]]||rk[sa[j]+i]^rk[sa[j-1]+i]) up++;
tmp[sa[j]]=up;
}
memcpy(rk,tmp,sizeof tmp);
}
for(int i=1;i<=n;i++) cout<<sa[i]<<" ";
return 0;
}
height
h递推性质:h[i]≥h[i−1]−1
height 区间最小值定理:任意两个后缀的 LCP 等于它们排名区间内 height 数组的最小值
int height[N];
void getHeight(){
int k=0;
for(int i=1;i<=n;i++){
if(rk[i]==1){
k=height[rk[i]]=0;
continue;
}
if(k) k--;
int j=sa[rk[i]-1];
while(i+k<=n&&j+k<=n&&c[i+k]==c[j+k]) k++;
height[rk[i]]=k;
}
}
SAM功能比SA丰富许多,但是SA仍有一些优势:
SA相比SAM最核心的不可替代优势是 LCP(最长公共前缀)查询。配合height数组,SA能 O(1) 回答任意后缀的LCP,而SAM需要求Parent树的LCA,实现繁琐得多。
此外,SA还有两大绝对优势:
无视字符集:处理整数、Unicode等大字符集时内存稳定,SAM则可能内存爆炸。
天生有序:直接输出所有后缀的字典序排名,SAM做不到这一点。
而SA掌握以上内容即可,其余建议使用SAM

浙公网安备 33010602011771号