想学这个算法很久了
省选结束后终于下定决心好好学学!
先推荐两篇文章
http://hi.baidu.com/myidea/item/142c5cd45901a51820e25039?qq-pf-to=pcqq.group
http://blog.csdn.net/huyuncong/article/details/7583214
学好后缀自动机的关键就是理解其转移边与父亲变得不同意义。
同时理解一些后缀树的性质有助于我们理解后缀自动机的工作原理。
1、nsubstr
求各个长度的子串最多出现了多少次。
后缀自动机上的每个节点能够匹配的字符串的长度一定是从他能匹配的最短的子串到最长的子串一段连续区间
考虑后缀树中如何求一个子串的出现次数,即将此子串在树上匹配后最后一个节点下的叶子节点数为答案。(先将所有点bfs一遍,再逆序扫描利用父亲边转移即可)
我们可以用这个点更新他能匹配的长度最大值,很明显长度为i的可以更新长度为i-1的
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int Maxn=500000;
char S[Maxn];
int son[Maxn][26],fa[Maxn],len[Maxn];
int r[Maxn],f[Maxn],s[Maxn],t[Maxn];
int n,cnt,last,i,j;
void add(int x){
int c = S[x]-'a';
int np = ++cnt, p = last;
last = np; len[np] = x;
for (;p&&!son[p][c];p=fa[p]) son[p][c] = np;
if (!p) fa[np] = 1;
else
{
int q = son[p][c];
if (len[p]+1==len[q]) fa[np] = q;
else
{
int nq = ++cnt; len[nq] = len[p]+1;
memcpy(son[nq],son[q],sizeof(son[q]));
fa[nq] = fa[q];
fa[np] = fa[q] = nq;
for (;son[p][c]==q;p=fa[p]) son[p][c]=nq;
}
}
}
int main(){
freopen("spoj8222.in","r",stdin);
freopen("spoj8222.out","w",stdout);
scanf("%s",S+1);
n = strlen(S+1);
last = ++cnt;
for (i=1;i<=n;i++) add(i);
for (i=1,j=1;i<=n;i++)
j = son[j][S[i]-'a'], r[j]++;
for (i=1;i<=cnt;i++) s[len[i]]++;
for (i=1;i<=n;i++) s[i]+=s[i-1];
for (i=1;i<=cnt;i++) t[s[len[i]]--]=i;
for (i=cnt;i>0;i--) r[fa[t[i]]]+=r[t[i]];
for (i=1;i<=cnt;i++) f[len[i]]=max(f[len[i]],r[i]);
for (i=n-1;i>0;i--) f[i]=max(f[i],f[i+1]);
for (i=1;i<=n;i++) printf("%d\n",f[i]);
return 0;
}
2、spoj lcs
求两个串的最长公共子串
把一个串建后缀自动机,另一个串在这个自动机场匹配即可,匹配失败可以按照父亲边向上跳(和ac自动机比较像)。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int Maxn=500005;
char S[Maxn];
int son[Maxn][26],fa[Maxn],len[Maxn];
int n,i,j,last,cnt,sum,ans;
void add(int x){
int c=S[i]-'a';
int np = ++cnt, p = last;
last = np; len[np] = x;
for (;p&&son[p][c]==0;p=fa[p]) son[p][c]=np;
if (!p) fa[np] = 1;
else
{
int q = son[p][c];
if (len[p]+1 == len[q]) fa[np] = q;
else
{
int nq = ++cnt; len[nq] = len[p]+1;
memcpy(son[nq],son[q],sizeof(son[q]));
fa[nq] = fa[q];
fa[np] = fa[q] = nq;
for (;son[p][c]==q;p=fa[p]) son[p][c]=nq;
}
}
}
int main(){
freopen("spoj.in","r",stdin);
freopen("spoj.out","w",stdout);
scanf("%s",S+1);
n = strlen(S+1);
last = ++cnt;
for (i=1;i<=n;i++) add(i);
scanf("%s",S+1);
n = strlen(S+1);
for (i=1,j=1,sum=0;i<=n;i++){
int c=S[i]-'a';
for (;j&&son[j][c]==0;sum=len[j=fa[j]]);
if (son[j][c]) sum++, j=son[j][c];
if (j==0) j=1;
ans = max(ans, sum);
}
printf("%d\n",ans);
return 0;
}
3、spoj lcs2
求不超过十个串的最长公共子串
还是把第一个串建立后缀自动机,剩下的串在自动机上匹配,每个节点上记录那些点匹配及匹配长度,取所有串的最小值即为这个点的最长子串长,取所有串上的最大值即可。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int Maxn=200005;
char S[10][Maxn];
int fa[Maxn],son[Maxn][26],len[Maxn];
int f[Maxn][10],s[Maxn],t[Maxn];
int N,cnt,i,j,k,sum,n,last,ans;
void add(int x){
int c = S[0][x]-'a';
int np = ++cnt, p = last;
last = np; len[np] = x+1;
for (;p&&son[p][c]==0;p=fa[p]) son[p][c]=np;
if (!p) fa[np] = 1;
else
{
int q = son[p][c];
if (len[q] == len[p]+1) fa[np] = q;
else
{
int nq = ++cnt; len[nq] = len[p]+1;
memcpy(son[nq],son[q],sizeof(son[q]));
fa[nq] = fa[q];
fa[np] = fa[q] = nq;
for (;p&&son[p][c]==q;p=fa[p]) son[p][c]=nq;
}
}
}
int main(){
//freopen("spoj.in","r",stdin);
//freopen("spoj.out","w",stdout);
while (~scanf("%s",S[N])) N++;
n = strlen(S[0]);
last = ++cnt;
for (i=0;i<n;i++) add(i);
for (i=1;i<N;i++){
n = strlen(S[i]);
for (j=0,k=1,sum=0;j<n;j++){
int c = S[i][j]-'a';
for (;k&&son[k][c]==0;sum=len[k=fa[k]]);
if (son[k][c]) k=son[k][c], f[k][i]=max(f[k][i],++sum);
if (k==0) k=1;
}
}
n = len[last];
for (i=1;i<=cnt;i++) s[len[i]]++;
for (i=1;i<=n;i++) s[i]+=s[i-1];
for (i=1;i<=cnt;i++) t[s[len[i]]--]=i;
for (i=cnt;i>0;i--)
for (j=1;j<N;j++)
f[fa[t[i]]][j] = max( f[fa[t[i]]][j], min(f[t[i]][j],len[fa[t[i]]]) );
for (i=1;i<=cnt;i++){
int tmp=10000000;
for (j=1;j<N;j++)
tmp = min(tmp,f[i][j]);
ans = max(ans,tmp);
}
printf("%d\n",ans);
return 0;
}
4、spoj sublex
求严格的第k小子串
我们现在需要知道每个节点向下可以在匹配出多少个子串,可以利用转移边dp出答案,然后按照从小到大的顺序扫描转移即可。
注意多组询问,直接扫描'a'-'z'可能会超时,把儿子压缩一下减少每次扫描的数量
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int Maxn=200005;
char S[Maxn],lt[Maxn];
int son[Maxn][26],len[Maxn],fa[Maxn],size[Maxn];
int n,q,k,i,j,cnt,last,ct,s[Maxn],t[Maxn];
void add(int x){
int c = S[x]-'a';
int np = ++cnt, p = last;
last = np; len[np] = x; lt[np] = 'a'+c;
for (;p&&son[p][c]==0;p=fa[p]) son[p][c]=np;
if (!p) fa[np] = 1;
else
{
int q = son[p][c];
if (len[p]+1 == len[q]) fa[np] = q;
else
{
int nq = ++cnt; len[nq] = len[p]+1;
memcpy(son[nq],son[q],sizeof(son[q]));
fa[nq] = fa[q]; fa[np] = fa[q] = nq;
for (;p&&son[p][c]==q;p=fa[p]) son[p][c]=nq;
lt[nq] = 'a'+c;
}
}
}
void answer_questions(){
scanf("%d",&q);
while (q--){
scanf("%d",&k);
//if (k>=size[1]) k=size[1]-1;
for (i=1;k!=0;i=son[i][j],k--){
for (j=0;j<26&&son[i][j];j++)
if (k>size[son[i][j]]) k-=size[son[i][j]];
else break;
printf("%c",lt[son[i][j]]);
}
printf("\n");
}
}
int main(){
//freopen("sublex.in","r",stdin);
//freopen("sublex.out","w",stdout);
scanf("%s",S+1);
n = strlen(S+1);
last = ++cnt;
for (i=1;i<=n;i++) add(i);
for (i=1;i<=cnt;i++) s[len[i]]++;
for (i=1;i<=n;i++) s[i]+=s[i-1];
for (i=1;i<=cnt;i++) t[s[len[i]]--]=i;
for (i=cnt;i>0;i--){
size[t[i]] = 1;
for (j=0;j<26;j++)
size[t[i]] += size[son[t[i]][j]];
}
for (i=1;i<=cnt;i++)
for (j=0,ct=0;j<26;j++)
if (son[i][j]) son[i][ct++]=son[i][j];
answer_questions();
return 0;
}
5、codeforces 235C
陈老师的题,先跪orz
把母串建立后缀自动机,预处理出每个节点匹配的穿在原串中出现了多少次(用第1题的方法)。
找到询问串的循环节(每个串的后移最早出现循环就出现在第一个循环节上),在串尾复制一遍
将处理好的串在自动机上匹配。
每次走到匹配好的点,需要沿着父亲边跳直到刚好卡到串长(保证计数不漏),将出现次数加到答案中
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int Maxn=2000005;
char S[Maxn],T[Maxn];
int son[Maxn][26],len[Maxn],fa[Maxn],pre[Maxn];
int n,last,cnt,i,j,k,q,sum,ans,r[Maxn],b[Maxn],t[Maxn];
void add(int x){
int c = S[x]-'a';
int np = ++cnt, p = last;
last = np; len[np] = x;
for (;p&&son[p][c]==0;p=fa[p]) son[p][c] = np;
if (!p) fa[np] = 1;
else
{
int q = son[p][c];
if (len[p]+1 == len[q]) fa[np] = q;
else
{
int nq = ++cnt; len[nq] = len[p]+1;
memcpy(son[nq],son[q],sizeof(son[nq]));
fa[nq] = fa[q]; fa[np] = fa[q] = nq;
for (;p&&son[p][c]==q;p=fa[p]) son[p][c]=nq;
}
}
}
int calc_repetend(){ //计算循环节
pre[1] = 0;
for (int i=2,j=0;i<=n;i++){
while (j&&T[j+1]!=T[i]) j=pre[j];
if (T[j+1]==T[i]) j++;
pre[i] = j;
}
int ret = n-pre[n];
if (n%ret) ret = n;
return ret;
}
int main(){
//freopen("235C.in","r",stdin);
//freopen("235C.out","w",stdout);
scanf("%s",S+1);
n = strlen(S+1);
last = ++cnt;
for (i=1;i<=n;i++) add(i);
for (i=1,j=1;i<=n;i++)
j=son[j][S[i]-'a'], r[j]++;
for (i=1;i<=cnt;i++) b[len[i]]++;
for (i=1;i<=n;i++) b[i]+=b[i-1];
for (i=1;i<=cnt;i++) t[b[len[i]]--]=i;
for (i=cnt;i>0;i--)
r[fa[t[i]]] += r[t[i]];
scanf("%d",&q);
while (q--){
scanf("%s",T+1);
n = strlen(T+1);
k = calc_repetend();
for (i=n+1;i<n+k;i++) T[i]=T[i-n];
for (i=1,j=1,sum=0,ans=0;i<n+k;i++){
int c = T[i]-'a';
for (;j&&son[j][c]==0;sum=len[j=fa[j]]);
if (son[j][c]) j = son[j][c], sum++;
if (j==0) j=1;
if (i>=n && sum>=n){
while (len[fa[j]]>=n)
sum = len[j=fa[j]];
ans += r[j];
}
}
printf("%d\n",ans);
}
return 0;
}
6、JLOI2015 弦论
求严格/不严格的第k小串。
其实是sublex的升级版。
严格的不用讲了吧,不严格的其实也很简单。
回顾一下“严格”其实是在说每个节点只匹配了一个点,不严格的话就是匹配了他在母串中的出现次数。
剩下的还用说吗?
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int Maxn=1000005;
char S[Maxn],lt[Maxn];
int len[Maxn],fa[Maxn],son[Maxn][26];
int b[Maxn],t[Maxn];
int n,cnt,last,i,j,K,T;
long long r[Maxn],s[Maxn];
void add(int x){
int c = S[x] - 'a';
int np = ++cnt, p = last;
last = np; len[np] = x; lt[np] = S[x];
for (;p&&son[p][c]==0;p=fa[p]) son[p][c]=np;
if (!p) fa[np] = 1;
else
{
int q = son[p][c];
if (len[p]+1 == len[q]) fa[np] = q;
else
{
int nq = ++cnt; len[nq] = len[p]+1;
memcpy(son[nq],son[q],sizeof(son[q]));
fa[nq] = fa[q]; fa[np] = fa[q] = nq;
for (;p&&son[p][c]==q;p=fa[p]) son[p][c]=nq;
lt[nq] = S[x];
}
}
r[ last ] ++;
}
int main(){
//freopen("3998.in","r",stdin);
//freopen("3998.out","w",stdout);
scanf("%s",S+1);
n = strlen(S+1);
last = ++cnt;
for (i=1;i<=n;i++) add(i);
scanf("%d%d",&T,&K);
//for (i=1;i<=cnt;i++) r[i]++;
for (i=1;i<=cnt;i++) b[len[i]]++;
for (i=1;i<=n;i++) b[i]+=b[i-1];
for (i=1;i<=cnt;i++) t[b[len[i]]--]=i;
for (i=cnt;i>0;i--)
r[fa[t[i]]] += r[t[i]];
if (T==0)
for (i=1;i<=cnt;i++) r[i]=(bool)r[i];
r[1] = 0;
for (i=cnt;i>0;i--){
s[t[i]] = r[t[i]];
for (j=0;j<26;j++)
s[t[i]] += s[son[t[i]][j]];
}
s[0] = r[0] = 0;
if (s[1]<K) {printf("-1\n"); return 0;}
for (i=1;K>0;K-=r[i]){
for (j=0;j<26;j++)
if (s[son[i][j]]<K) K -= s[son[i][j]];
else break;
i=son[i][j];
printf("%c",lt[i]);
}
printf("\n");
return 0;
}