牛客多校4 - Count New String(序列自动机+广义后缀自动机)

题目链接:点击查看

题目大意:

牛客多校4 - Count New String(序列自动机+广义后缀自动机)_第1张图片

题目分析:首先观察到集集合 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,后缀的长度从小到大可以表示为:

  1. a
  2. aa
  3. aaa
  4. ......
  5. aaa...aaa
  6. bbbb...bbb
  7. ccccc...ccc
  8. 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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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

 

你可能感兴趣的:(字符串处理)