LOJ3103. 「JSOI2019」节日庆典

给定一个字符串,对于每个前缀,求其最小循环同构串的开头(位置为第二关键字)。

(nle 3*10^6)


用到了一点Significant Suffixes的知识。参考博客:https://www.luogu.com.cn/blog/command-block/border-li-lun-xiao-ji

Significant Suffixes,最小后缀。

(minsuf(s))表示(s)的最小后缀,

(Ssuf(s)={vin suf(s)|exist r,minsuf(sr)=vr})。也就是说,再加入某个字符串(r),有可能成为最小后缀的后缀。

性质1:如果(u,vin Ssuf(s),|u|<|v|),则(u)(v)的前缀。

性质2:如果(u,vin Ssuf(s),|u|<|v|),则(2|u|<|v|)

证明:如果(|u|<|v|le 2|u|),设(u=AB,v=AAB)

如果存在后缀(R),使(ABR<AABR),则(BR<ABR)。由于(B)也是一个后缀,它不是最小后缀。矛盾。

推论:(|Ssuf(s)|=O(lg n))

那么可以对每个前缀维护(Ssuf)

每次在集合中加入一个字符(c)。然后对于相邻的字符串(s_i,s_{i+1})(设(|s_i|>|s_{i+1}|)):

  1. 如果(|s_{i+1}|)不是(|s_i|)的前缀,则比较大小,保留小的那个。
  2. 否则,如果(|s_i|le 2|s_{i+1}|),则删去(s_{i+1})

维护集合,枚举比较即可。

因为比较时一个是另一个的前缀,所以可以据此优化一下比较方式。用exkmp预处理一下,就可以(O(1))出来了。

时间(O(nlg n))

另一种高级的Lyndon word做法:参考博客https://www.luogu.com.cn/blog/qwaszx/solution-p5334

(关于这个做法本人还有很多搞不懂的地方,如果有大佬路过请求指导)

假设对某个前缀求其答案,设这个前缀Lyndon分解之后长成(w_1^{k_1}w_2^{k_2}dots w_{m}^{k_m})

显然选择的最小循环串开头是某个Lyndon串的开头,可以表示成:(w_i^kw_{i+1}^{k_{i+1}}dots w_{m}^{k_m})

结论1:此时(k=k_i)

证明:即证(uv<u^2vor uv<v)。如果(uge v),则有(u^2v>uv>v);否则:如果(u)不是(v)的前缀,则有(uv<v);如果(u)(v)的前缀,把前缀公共部分截掉,剩下是个子问题递归证明。

这个结论对于任意字符串适用

结论2:(u,v)是任意串,(w)是Lyndon word,如果(u<w,v<w),则(uv<w)

(S_i=w_i^{k_i}dots w_m^{k_m})。于是有(w_i^{k_i}ge w_i>S_{i+1})

显然(S_min Ssuf),如果(S_{m-1}in Ssuf),则(2|S_m|>|S_{m-1}|),此时有(S_m)(w_{m-1}^{k_{m-1}})的前缀;再往前推,直到找到第一个(j),满足(S_{j+1})不是(w_{j}^{k_j})的前缀,那么(S_{j+1},dots,S_{m}in Ssuf),并且(S_1,S_2,dots,S_j otin Ssuf)

暴力地维护前缀的Lyndon word(单调栈),就可以得到(O(nlg n))的做法。

更优秀的做法:考虑Duval算法。在做Duval算法的过程中,前面的字符串分成了(S_1S_2),其中(S_1)已经分解好了,(S_2=wwdots wv),其中(v)(w)的前缀。注意到(S_1)不会对答案有影响,可以归纳:算法过程中,如果(wwdots wv)后面接了(c),使得(vc<w)(vc)不为(w)前缀,则要把前面这些(w)丢到(S_1)中,于是新的(S_2=vc)。由于(vc<w)(vc)不为(w)前缀,所以(w otin Ssuf)。于是对答案有影响的只有:第一个(w)的开头,以及(v)中的每个位置。

(a_i)表示前缀(i)的最小循环串的开头。设做Lyndon分解的三个指针为(i,j,k)(定义见Lyndon分解学习小记)。

(i)刚刚被重置时,令(a_i=i)

(s_k=s_j):找到(k)在第一个(w)中对应的位置,设为(t)(t=i+(j-i)mod (k-j)))。令(a_k)(k-(t-a_t))(即从第一个(w)中的位置对应到(v)中的位置)和(i)中的最优解。(如果(a_t<i)则不优,不算)

(s_k>s_j):令(a_k=i)

其余的部分和普通的算法流程一样。注意对(a_k)(a_i))操作时,都有(a_k)没有被操作过作为前提。

正确性:(s[1,k])中,开头在(v)中的循环串,等于(s[1,t])中,开头在(w[1,|v|])的循环串末尾加若干个(u),其中(u)(w)的一个循环同构串。(a_t)(s[1,t])的答案,如果没有相等的循环串,那么对应过来也最优;至于如果有相等的循环串的情况,局限于目前的水平我并没有分析出来。另外为什么要以(a_k)没有被操作过为前提,我也不清楚。

同样也需要预处理exkmp来帮助比大小。时间(O(n))


using namespace std;
#include <bits/stdc++.h>
#define N 3000005
int n;
void print(int x){
	static int st[20];
	int tp=0;
	if (x==0)
		putchar('0');
	else{
		while (x)
			st[++tp]=x%10,x/=10;
		while (tp)
			putchar(st[tp--]+'0');
	}
}
char s[N];
int nxt[N];
void exkmp(){
	int pos=0,mr=0;
	nxt[0]=n;
	for (int i=1;i<n;++i){
		if (i<=mr)
			nxt[i]=min(nxt[i-pos],mr-i);
		while (i+nxt[i]<n && s[i+nxt[i]]==s[nxt[i]])
			nxt[i]++;
		if (i+nxt[i]>mr)
			mr=i+nxt[i],pos=i;
	}
}
int st[50],tp;
bool cmp(int u,int v,int l){
	u+=l-v+1,v=0;
	if (u+nxt[u]-1<l)
		return s[u+nxt[u]]<=s[nxt[u]];
	v+=l-u+1,u=0;
	if (v+nxt[v]-1<l)
		return s[nxt[v]]<=s[v+nxt[v]];
	return 1;
}
int main(){
	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	scanf("%s",s);
	n=strlen(s);
	exkmp();
	for (int i=0;i<n;++i){
		st[++tp]=i;
		st[tp+1]=-1;//pay attention	
		for (int j=tp;j>=2;--j){
			if (s[i]<s[st[j-1]+i-st[j]]){
				st[j-1]=st[j];
				st[j]=-1;
			}
			else if (s[i]>s[st[j-1]+i-st[j]] || 2*(i-st[j]+1)>=(i-st[j-1]+1)){//the order of "if"
				st[j]=st[j+1];
				st[j+1]=-1;
			}
		}
		int tmp=tp;
		tp=0;
		for (int j=1;j<=tmp;++j)
			if (st[j]!=-1)
				st[++tp]=st[j];
//		for (int j=1;j<=tp;++j)
//			printf("%d ",st[j]);
//		printf("
");
//		continue;
		int ans=st[tp];
		for (int j=tp-1;j>=1;--j)
			if (cmp(st[j],ans,i))
				ans=st[j];
		print(ans+1),putchar(' ');
	}
	return 0;
}
using namespace std;
#include <bits/stdc++.h>
#define N 3000005
int n;
void print(int x){
	static int st[20];
	int tp=0;
	if (x==0)
		putchar('0');
	else{
		while (x)
			st[++tp]=x%10,x/=10;
		while (tp)
			putchar(st[tp--]+'0');
	}
}
char s[N];
int nxt[N];
void exkmp(){
	int pos=0,mr=0;
	nxt[0]=n;
	for (int i=1;i<n;++i){
		if (i<=mr)
			nxt[i]=min(nxt[i-pos],mr-i);
		while (i+nxt[i]<n && s[i+nxt[i]]==s[nxt[i]])
			nxt[i]++;
		if (i+nxt[i]>mr)
			mr=i+nxt[i],pos=i;
	}
}
bool cmp(int u,int v,int l){
	u+=l-v+1,v=0;
	if (u+nxt[u]-1<l)
		return s[u+nxt[u]]<=s[nxt[u]];
	v+=l-u+1,u=0;
	if (v+nxt[v]-1<l)
		return s[nxt[v]]<=s[v+nxt[v]];
	return 1;
}
int a[N];
int main(){
	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	scanf("%s",s);
	n=strlen(s);
	exkmp();
	int i=0;
	while (i<n){
		int k=i+1,j=i;
		if (!a[i])
			a[i]=i;
		while (k<n && s[k]>=s[j]){
			if (s[k]==s[j]){
				int t=i+(j-i)%(k-j);
				if (!a[k]){
					if (a[t]<i)
						a[k]=i;
					else
						a[k]=(cmp(i,k-(t-a[t]),k)?i:k-(t-a[t]));
				}
				++j,++k;
			}
			else{
				if (!a[k])
					a[k]=i;
				j=i,++k;
			}
		}
		while (i<=j)
			i+=k-j;
	}
	for (int i=0;i<n;++i)
		print(a[i]+1),putchar(' ');
	return 0;
}
原文地址:https://www.cnblogs.com/jz-597/p/14601503.html