题意:给你一个字符串,问这个字符串中有多少个子串出现了k次
上一篇的简化版。。
不过这个人的代码很清晰
参考http://www.cnblogs.com/weeping/p/7503972.html
据说sam的指针版会被卡。。看来还是要各种版本都会写才可以。。
#include <bits/stdc++.h>
using namespace std;
char ss[100004];
int ans;
struct SAM
{
static const int MAXN = 100001<<1;//大小为字符串长度两倍
static const int LetterSize = 26;
int tot, last, ch[MAXN][LetterSize], fa[MAXN], len[MAXN];
int sum[MAXN], tp[MAXN], cnt[MAXN]; //sum,tp用于拓扑排序,tp为排序后的数组
void init( void)
{
last = tot = 1;
len[1] = 0;
memset(ch,0,sizeof ch);
memset(fa,0,sizeof fa);
memset(cnt,0,sizeof cnt);
}
void add( int x)
{
int p = last, np = last = ++tot;
len[np] = len[p] + 1, cnt[last] = 1;
while( p && !ch[p][x]) ch[p][x] = np, p = fa[p];
if( p == 0)
fa[np] = 1;
else
{
int q = ch[p][x];
if( len[q] == len[p] + 1)
fa[np] = q;
else
{
int nq = ++tot;
memcpy( ch[nq], ch[q], sizeof ch[q]);
len[nq] = len[p] + 1, fa[nq] = fa[q], fa[q] = fa[np] = nq;
while( p && ch[p][x] == q) ch[p][x] = nq, p = fa[p];
}
}
}
void toposort( void)
{
for(int i = 1; i <= len[last]; i++) sum[i] = 0;
for(int i = 1; i <= tot; i++) sum[len[i]]++;
for(int i = 1; i <= len[last]; i++) sum[i] += sum[i-1];
for(int i = 1; i <= tot; i++) tp[sum[len[i]]--] = i;
}
} sam;
int main(void)
{
//freopen("in.acm","r",stdin);
int t,k;cin>>t;
while(t--)
{
ans=0;
sam.init();
scanf("%d%s",&k,ss);
for(int i=0,len=strlen(ss);i<len;i++) sam.add(ss[i]-'a');
sam.toposort();
for(int i=sam.tot;i;i--)
{
int p=sam.tp[i],fp=sam.fa[p];
sam.cnt[fp]+=sam.cnt[p];
if(sam.cnt[p]==k)
ans+=sam.len[p]-sam.len[fp];
}
printf("%d\n",ans);
}
return 0;
}
sa 的做法:
参考http://www.cnblogs.com/quintessence/p/7507923.html:
只需求出出现次数>=k和 >=k+1的个数。
对于求出现次数>=x的,如果x=1,则直接特判输出。
若不然,只需维护长度x-1的height序列(height序列长度x-1 则对应的后缀数即为x),维护序列中height的最小值,按下标从小到大扫,注意到每次更新,只有该最小值发生变大时,才会增加新的出现次数>=x次的子串。(不变或变小时当前的子串之前都计数过)
以此计算>=x >=x+1的相减即可。
感悟:
这种“只有当height变大的时候才更新的方式好像也是挺普遍的。”