LOJ3103. 「JSOI2019」节日庆典

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

\(n\le 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)=\{v\in suf(s)|\exist r,minsuf(sr)=vr\}\)。也就是说,再加入某个字符串\(r\),有可能成为最小后缀的后缀。

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

性质2:如果\(u,v\in 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(n\lg 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^2v\or uv<v\)。如果\(u\ge 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_m\in 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\notin Ssuf\)

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

更优秀的做法:考虑Duval算法。在做Duval算法的过程中,前面的字符串分成了\(S_1S_2\),其中\(S_1\)已经分解好了,\(S_2=ww\dots wv\),其中\(v\)\(w\)的前缀。注意到\(S_1\)不会对答案有影响,可以归纳:算法过程中,如果\(ww\dots wv\)后面接了\(c\),使得\(vc<w\)\(vc\)不为\(w\)前缀,则要把前面这些\(w\)丢到\(S_1\)中,于是新的\(S_2=vc\)。由于\(vc<w\)\(vc\)不为\(w\)前缀,所以\(w\notin 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("\n");
//		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;
}
posted @ 2021-03-31 15:00  jz_597  阅读(76)  评论(0编辑  收藏  举报