zoukankan      html  css  js  c++  java
  • 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;
    }
    
  • 相关阅读:
    冒泡排序法
    选择排序法
    pyhanlp 停用词与用户自定义词典
    pyhanlp 分词与词性标注
    第八九章 正态分布与超越正态
    深入浅出统计学第七章 几何分布,二项分布,柏松分布
    深入浅出统计学 第六章 排列与组合
    深入浅出统计学 第四五章 离散概率的计算与分布
    R语言简介与案例
    深入浅出统计学 第二三章 量度
  • 原文地址:https://www.cnblogs.com/jz-597/p/14601503.html
Copyright © 2011-2022 走看看