题目地址:https://cn.vjudge.net/problem/SPOJ-NSUBSTR
解题思路:
建立SAM,先统计每个状态的最长子串出现的次数(应该是这个意思吧,这个状态substring中的每个子串也都出现了相同的次数),每个状态st的substring中的子串长度区间为[maxlen[link[st]]+1,maxlen[st]]。
对于状态st1,substring(st1)中的子串长度满足ans[i]=ans[i+1],当i=maxlen[st1]时,i+1之后子串就会跳转到另一个状态st2,而且substring(st1)的最长子串s1是substring(st2)最长子串s2的后缀,s1的出现次数不会比s2少,所以在更新答案是依旧满足ans[i] = max(ans[i], ans[i+1])。
我。。。说的好啰嗦。。。。(读者请仔细看完这篇博客前面的原理部分,可以参照下面的图理解)
总之对应的代码就是酱紫的:
for(int i = 1; i <= num; i++) ans[maxlen[i]] = max(ans[maxlen[i]], cnt[i]); for(int i = len-1; i >= 1; i--) ans[i] = max(ans[i], ans[i+1]);
ac代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn = 1e6+100;
int maxlen[maxn<<1], trans[maxn<<1][30], link[maxn<<1];
int x[maxn<<1], y[maxn<<1], cnt[maxn<<1];
int num, last;//num表示状态数
char s[maxn];
int ans[maxn]={0};
void init()
{
num = last = 1;//起始状态的编号为1
maxlen[last] = link[last] = 0;//起始位置为空字符,长度为0
}
void insert(int id)
{
int z = ++num, p;
maxlen[z] = maxlen[last]+1;
for(p = last; p && !trans[p][id]; p = link[p]) trans[p][id] = z;
if(!p) link[z] = 1;//路径上不存在跳转
else//连接路径上已存在跳转
{
int x = trans[p][id];//第一个存在跳转的状态跳转去的状态
if(maxlen[x] == maxlen[p]+1) link[z] = x;//一个
else//多个
{
int y = ++num;
maxlen[y] = maxlen[p]+1;
memcpy(trans[y], trans[x], sizeof(trans[x]));
link[y] = link[x];
for(; p && trans[p][id] == x; p = link[p]) trans[p][id] = y;
link[x] = link[z] = y;
}
}
last = z;
cnt[z] = 1;
}
void count()
{
memset(x, 0, sizeof x);
for(int i = 1; i <= num; i++) x[maxlen[i]]++;
for(int i = 1; i <= num; i++) x[i] += x[i-1];
for(int i = 1; i <= num; i++) y[x[maxlen[i]]--] = i;
for(int i = num; i >= 1; i--)
cnt[link[y[i]]] += cnt[y[i]];//得到每个状态的字符串集出现的次数
}
void getans(int len)//统计长度为x的子串最多出现的次数
{
for(int i = 1; i <= num; i++) ans[maxlen[i]] = max(ans[maxlen[i]], cnt[i]);
for(int i = len-1; i >= 1; i--) ans[i] = max(ans[i], ans[i+1]);
}
int main()
{
scanf("%s", s);
int len = strlen(s);
init();
for(int i = 0; i < len; i++) insert(s[i]-'a');
count();
getans(len);
for(int i = 1; i <= len; i++)
printf("%d\n", ans[i]);
return 0;
}