【SPOJ8222】后缀自动机-重复子串(right数组小应用)

其实算法很容易想到,一个小地方的证明卡了很久orz... 链接:SPOJ8222
问题描述
给定字符串s,定义F(x)表示s的所有长度为x的子串中,重复出现次数最多子串在s中的出现的次数,两次出现可以有部分重叠。 现给定字符串s,求F(1),F(2),...,F(length(s)).
输入格式
一行,即一个字符串s。
输出格式
共length(s)行,每行一个数。第i行为F(i)。
样例输入
ababa
样例输出
3 2 2 1 1
提示
1<=length(s)<=250000 直接建立一个后缀自动机,然后我们利用right数组的定义,在主链(即直接构成原字符串的那条路径)先设定为1,因为这个集合中表示的子串就多一个前缀,之后我们考虑par树倒着全部叠加一遍(我们考虑每个节点的定义,right集合大小为到达该状态的全部所有路径的条数,其倒着加回来,类似于DP的考虑),即这个节点表示的所有子串所在原串中出现的所有次数(right集合大小)。 那么这个实现可以用加边+dfs,可以用基数排序(由于mx全部不超过len,且递增出现),甚至可以愚蠢地像我一样写了个stable_sort()(强行将O(n)搞成了O(n logn) orz过了才发现,不想改了) 然后倒着加回来。 最后再用每个结点上的r[i] 去更新F(mx[i])。但是我卡在这里想了很久,每个状态表示的min到max,为什么只用更新max就够了呢? 因为假如一个状态{abcd,bcd},为r[i],我们可能会去考虑是否要更新{b,c,d},F[3]这个状态。但是abcd出现了r[i]次,那么abc就至少会出现r[i]次,而我们考虑的bcd就一定只出现了r[i],会小于等于abc出现的次数。所以我们不需要考虑更新mx[i]以外的数。 由于本蒟蒻对于后缀自动机也不是特别透彻,思路也略乱,orz orz orz,考虑日后回顾修改。 code:
#include<stdio.h>
#include<bits/stdc++.h>

using namespace std;
const int maxn = 500005;
char ss[maxn]; 
int son[maxn][27],las=1,tot=1,rt=1,par[maxn];
int mx[maxn];
int push(int val)
{
	mx[++tot] = val;
	return tot;
}
int r[maxn];
int mmm[maxn];
void extend(int t)
{
	int p,np,q,nq;
	np = push(mx[las]+1);
	for(p=las;p&&(!son[p][t]);p=par[p]) son[p][t] = np;
	if(!p) par[np] = rt;
	else
	{
		q = son[p][t];
		if( mx[p]+1==mx[q] ) par[np] = q;
		else
		{
			nq = push( mx[p]+1 );
			memcpy(son[nq],son[q],sizeof(son[q]));
			par[nq] = par[q]; par[q] = par[np] =nq;
			for(;p&&son[p][t]==q;p=par[p]) son[p][t] = nq;
		}
	}
	las = np;
}
int f[maxn];
bool cmp1(int a,int b)
{
	return mx[a] < mx[b];
}
int main()
{
	scanf("%s",ss+1);
	int len=strlen(ss+1);
	for(int i=1;i<=len;i++) extend(ss[i]-'a');
	for(int i=1;i<=tot;i++) f[i] = i;
	stable_sort(f+1,f+1+tot,cmp1);
	int now = rt;
	for(int i=1;i<=len;i++)
	{
		now = son[now][ss[i]-'a'];
		r[now]++;
	}
	for(int i=tot;i>1;i--)
	{
		mmm[mx[f[i]]] = max( mmm[mx[f[i]]] , r[f[i]] );
		if(par[f[i]]!=rt) r[par[f[i]]] += r[f[i]];  
	}
	for(int i=1;i<=len;i++)
	{
		printf("%d\n",mmm[i]);
	}
}
   
posted @ 2018-05-23 19:13  Newuser233  阅读(17)  评论(0)    收藏  举报