题目链接:点击查看
题目大意:
题目分析:首先观察到集集合 A 中那个套娃的表示,外层的范围是 [ x1 , y1 ] ,内层是 [ x2 , y2 ] ,而内层的定义域实际上是包含在外层的定义域内的,这样外层的函数 f 其实是不起作用的,因为内层的函数 f 已经按照规则将区间集合 A 中 [ x1 , y1 ] 内的每个元素按照规则转换完毕了,再进行一次相同的转换,则就变的可有可无了,换句话说,每一个 f( S , x1 , y1 ) 实质上表示为字符串 s 的每个子串经过 f 函数转换后的字符串,这样题意就变成了求所有子串经过 f 函数的转换后,有多少个本质不同的字符串
上面转换后的题意还不够直接,通过观察可以发现,假设字符串 s 在经过 f 函数的转换后,得到新的字符串记为 ss ,不难看出字符串 s 的一个前缀在经过 f 函数的转换后,得到的仍然是 ss 的前缀
参考后缀与子串的关系,可以将所有的子串表示为任意一个后缀的前缀,同样题意转换为了所有经过 f 函数转换后的后缀中,有多少个本质不同的子串
这样题意就转换为了求 f( S , i , n ) ,i ∈ [ 1 , n ] ,求 n 个串中本质不同的子串,这个可以借助广义后缀自动机来完成,但是如果暴力为 n 个串建立 sam 的话,时空复杂度都会爆掉,再根据 f 函数的性质来考虑,可以发现,因为字符集的大小为 10,所以任意一个位置的字符,至多会被更新 9 次,最极端的情况下,假设字符串为 dcbaaa...aaa,后缀的长度从小到大可以表示为:
- a
- aa
- aaa
- ......
- aaa...aaa
- bbbb...bbb
- ccccc...ccc
- ddddd...ddd
这是最极端的情况,这样整体建立广义 sam 的时空复杂度也不会超过 10N
在建立广义 sam 时,可以借助序列自动机来实现,普通的序列自动机 nx[ i ][ j ] 记录的是位置 i 后(包括)首次出现字母 j 的位置,在本题中需要转换为:nx[ i ][ j ] 表示为记录位置 i 后(包括)首次大于等于字母 j 的位置,这样对于第 i 个位置的字母 j 来说,只需要暴力更新一下 [ i , nx[ i ][ j ] - 1 ] 就好了,nx[ i ][ j ] 往后的位置都可以直接从前面维护的 trie 树中拉下来继续用
时空复杂度都是 10N
代码:
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int N=1e6+100;
char s[N];
int tot,last,id[N],nx[N][10];
struct Node
{
int ch[10];
int fa,len;
}st[N<<1];
inline int newnode()
{
tot++;
for(int i=0;i<10;i++)
st[tot].ch[i]=0;
st[tot].fa=st[tot].len=0;
return tot;
}
void add(int x)
{
int p=last;
//
if(st[p].ch[x])
{
int q=st[p].ch[x];
if(st[q].len==st[p].len+1)
last=q;
else
{
int np=last=++tot;
st[np].len=st[p].len+1;
st[np].fa=st[q].fa;
st[q].fa=np;
for(int i=0;i<10;i++)
st[np].ch[i]=st[q].ch[i];
while(st[p].ch[x]==q)
st[p].ch[x]=np,p=st[p].fa;
}
return;
}
//
int np=last=++tot;
st[np].len=st[p].len+1;
while(p&&!st[p].ch[x])st[p].ch[x]=np,p=st[p].fa;
if(!p)st[np].fa=1;
else
{
int q=st[p].ch[x];
if(st[p].len+1==st[q].len)st[np].fa=q;
else
{
int nq=++tot;
st[nq]=st[q]; st[nq].len=st[p].len+1;
st[q].fa=st[np].fa=nq;
while(p&&st[p].ch[x]==q)st[p].ch[x]=nq,p=st[p].fa;//向上把所有q都替换成nq
}
}
}
void init()
{
last=1;
tot=0;
newnode();
}
int main()
{
#ifndef ONLINE_JUDGE
// freopen("data.in.txt","r",stdin);
// freopen("data.out.txt","w",stdout);
#endif
// ios::sync_with_stdio(false);
init();
scanf("%s",s+1);
int n=strlen(s+1);
for(int i=0;i<10;i++)
nx[n+1][i]=n+1;
for(int i=n;i>=1;i--)//nx[i][j]:第i个位置(包括)后首次出现大于等于j的位置
{
for(int j=0;j<10;j++)
nx[i][j]=nx[i+1][j];
nx[i][s[i]-'a']=i;
for(int j=8;j>=0;j--)
nx[i][j]=min(nx[i][j],nx[i][j+1]);
}
id[n+1]=last;
for(int i=n;i>=1;i--)
{
int pos=nx[i+1][s[i]-'a'];
last=id[pos];
for(int j=i;j<pos;j++)
add(s[i]-'a');
id[i]=last;
}
LL ans=0;
for(int i=1;i<=tot;i++)
ans+=st[i].len-st[st[i].fa].len;
printf("%lld\n",ans);
return 0;
}