题意:
求原串S中出现次数在[A,B]之间的子串的个数
解析:
后缀自动机的经典应用。
我是通过结合了求不同子串个数的d[]和不同子串出现次数的sz[]来做的。
在拓扑序遍历后缀自动机的时候,我们求不同子串个数是默认d[]初始为1 的,因为默认一个状态点本身也是一条可行路径
但是这里我们是有限定次数的,所以对于一个状态点v表示的子串出现次数sz[v]不在[A,B]时,那么他本身就不是一条可行路径
所以不需要加1,然后这样递推上去就行了(注意这里d[i]表示的是从状态i出发,不同的子串的数目,即不同的路径数)
其实还有一种更简单的做法,直接将len相减来求,res+=sam.e[v].len-sam.e[sam.e[v].f].len;
因为对于每一个状态点v表示的字符串是唯一的,不可能在其他的状态点出现,所以不会重复,那么一个状态点
表示的字符串是在[A,B]里面的话,我们直接把这些字符串加到答案里面就可以了,因为这些字符串不可能在其他地方出现了。
其实上面那个解法也用到了这个性质,不将不满足的状态点的d[]+1,就是因为他不可能在其他地方出现了,所以一定是不行的。
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned int uint;
const int N = 1e5+10;
struct Node {
int f, len, ch[26];
void init() {
len = 0, f = -1;
memset(ch, 0xff, sizeof (ch));
}
};
ll sz[N<<1]; //sz[i]表示状态i的right(i)的大小,即状态i表示的字符串出现的次数
struct SAM {
Node e[N<<1];
int idx, last;
void init() {
idx = 0;
last = newnd();
}
int newnd() {
e[idx].init();
return idx++;
}
void add(int c) {
int end = newnd();
int p = last;
e[end].len = e[p].len + 1;
for (; p != -1 && e[p].ch[c] == -1; p = e[p].f) {
e[p].ch[c] = end;
}
if (p == -1) e[end].f = 0;
else {
int nxt = e[p].ch[c];
if (e[p].len + 1 == e[nxt].len) e[end].f = nxt;
else {
int nd = newnd();
e[nd] = e[nxt];
e[nd].len = e[p].len + 1;
e[nxt].f = e[end].f = nd;
for (; p != -1 && e[p].ch[c] == nxt; p = e[p].f) {
e[p].ch[c] = nd;
}
}
}
sz[end]=1;
last = end;
}
};
SAM sam;
char str[N];
int ws[N];
int tp[N<<1];
ll d[N<<1]; //d[i]表示从状态i出发,不同的子串的数目,即不同的路径数
int main()
{
int A,B;
while(scanf("%s%d%d",str,&A,&B)!=EOF)
{
sam.init();
int len=strlen(str);
for(int i=0;str[i];i++) sam.add(str[i]-'A'); //插入
//对sam的节点按照len,从小到大排序重新标号,即给定节点的拓扑序
for(int i=0;i<=len;i++) ws[i]=0;
for(int i=1;i<sam.idx;i++) ws[sam.e[i].len]++;
for(int i=1;i<=len;i++) ws[i]+=ws[i-1];
for(int i=sam.idx-1;i>0;i--) tp[ws[sam.e[i].len]--]=i;
//按照拓扑序进行遍历
//ll res=0;
for(int i=sam.idx-1;i;i--)
{
int v=tp[i];
sz[sam.e[v].f]+=sz[v];
if(sz[v]>=A&&sz[v]<=B) d[v]++;
//if(sz[v]>=A&&sz[v]<=B) res+=sam.e[v].len-sam.e[sam.e[v].f].len;
//我看其他也有直接将len相减来求的,这个方法书是可以A的,直接输出res就可以了
for(int j=0;j<26;j++)
if(sam.e[v].ch[j]!=-1) d[v]+=d[sam.e[v].ch[j]];
}
ll ans=0;
for(int i=0;i<26;i++)
{
if(sam.e[0].ch[i]!=-1) ans+=d[sam.e[0].ch[i]];
}
for(int i=0;i<sam.idx;i++) sz[i]=0,d[i]=0;
printf("%lld\n",ans);
getchar();
}
}