zoukankan      html  css  js  c++  java
  • poj 3693 Maximum_repetition_substring

    题目链接
    题解:
    找循环节个数最多的子串并且字典序最小。
    先从简单入手,不考虑子串字典序。
    暴力做法是(O(n^2))枚举子串,用MP的fail数组求出其最小周期长度,用Len/T即循环节个数,这里注意必须整除才能更新答案。复杂度是(O(n^2))的。
    假设一个子串循环节个数为k,且每个周期的长度是T,那么该子串的长度为kT,且至少包含k个是T的整倍数的下标。比如s[2,7]=ababab是一个T=2,k=3的子串,里面包含了1T,2T,3T这三个下标。
    枚举T,再枚举i,使得下标iT和(i+1)T合法,求一下(a=lcp(iT,(i+1)T))(b=lcs(iT,(i+1)T)),如果a+b>T证明此时一定有一个子串的循环节个数大于1,且包含iT和(i+1)T。可以知道这个子串L最小到iT-b+1,R最大到(i+1)T+a-1,[L,R]内包含多少个T,就是当前最大值,用p=(R-L+1)/T更新答案即可。
    如果还需要考虑字典序,我们就是在[L,R-p*T+1]之中找一个Rank最小的,那个就是包含iT,(i+1)T的最小字典序的且循环节个数最大的子串。
    所以需要用后缀数组预处理Height,Rank数组和反串的Height数组,构建这三个数组的ST表,这样就可以O(1)得知答案了。枚举T再枚举i的复杂度是(O(nln n)),查询的复杂度是常数较大的(O(1))。总复杂度为(O(nln n))
    PS:细节较多,看代码更适合理解。

    code

    #include<stdio.h>
    #include<string.h>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    #define fcl fclose(stdin); fclose(stdout); return 0
    int n;
    char str[200010];
    int C[200010],Ws[200010],Rank[200010],Sa[200010],Height[200010],ST[100010][19];
    int Rank2[200010],Sa2[200010],Height2[200010],ST2[100010][19];
    int Table[100010],st[100010][19];
    void da(){
        //构建原串的Height数组和Rank数组
    	int i,j,c,k,m=200,p;
    	memset(Ws,0,(m+1)<<2);
    	for(i=0;i<n;++i) Ws[Rank[i]=str[i]]++;
    	for(i=1;i<=m;++i) Ws[i]+=Ws[i-1];
    	for(i=0;i<n;++i) Sa[--Ws[Rank[i]]]=i;
    	for(c=1;c<=n;c<<=1,m=p){
    		for(j=0,i=n-c;i<n;++i) C[j++]=i;
    		for(i=0;i<n;++i) if(Sa[i]>=c) C[j++]=Sa[i]-c;
    		memset(Ws,0,(m+1)<<2);
    		for(i=0;i<n;++i) Ws[Rank[i]]++;
    		for(i=1;i<=m;++i) Ws[i]+=Ws[i-1];
    		for(i=n-1;i>=0;--i) Sa[--Ws[Rank[C[i]]]]=C[i];
    		for(C[Sa[0]]=0,i=1,p=0;i<n;++i)
    			C[Sa[i]]=(Rank[Sa[i]]==Rank[Sa[i-1]]&&Rank[Sa[i]+c]==Rank[Sa[i-1]+c]?p:++p);
    		memcpy(Rank,C,n<<2);
    	}
    	for(i=0,j=0,k=0;i<n;++i){
    		if(Rank[i]==0) continue;
    		for(k=(k?k-1:0),j=Sa[Rank[i]-1];str[i+k]==str[j+k];++k);
    		Height[Rank[i]]=k;
    	}
    	for(i=n-1;i>=0;--i){
    		ST[i][0]=Height[i];
    		for(j=0;j<18;++j){
    			if(i+(1<<j)<n) ST[i][j+1]=min(ST[i][j],ST[i+(1<<j)][j]);
    			else ST[i][j+1]=ST[i][j];
    		}
    		st[i][0]=i;
    		for(int j=0;j<18;++j){
    			if(i+(1<<j)<n) st[i][j+1]=(Rank[st[i][j]]<Rank[st[i+(1<<j)][j]]?st[i][j]:st[i+(1<<j)][j]);
    			else st[i][j+1]=st[i][j];
    		}
    	}
    }
    void da2(){
        //只不过是为了构建反串的Height而已,除了数组名字外其他的几乎同上
    	int i,j,c,k,m=200,p;
    	memset(Ws,0,(m+1)<<2);
    	for(i=0;i<n;++i) Ws[Rank2[i]=str[i]]++;
    	for(i=1;i<=m;++i) Ws[i]+=Ws[i-1];
    	for(i=0;i<n;++i) Sa2[--Ws[Rank2[i]]]=i;
    	for(c=1;c<=n;c<<=1,m=p){
    		for(j=0,i=n-c;i<n;++i) C[j++]=i;
    		for(i=0;i<n;++i) if(Sa2[i]>=c) C[j++]=Sa2[i]-c;
    		memset(Ws,0,(m+1)<<2);
    		for(i=0;i<n;++i) Ws[Rank2[i]]++;
    		for(i=1;i<=m;++i) Ws[i]+=Ws[i-1];
    		for(i=n-1;i>=0;--i) Sa2[--Ws[Rank2[C[i]]]]=C[i];
    		for(C[Sa2[0]]=0,i=1,p=0;i<n;++i)
    			C[Sa2[i]]=(Rank2[Sa2[i]]==Rank2[Sa2[i-1]]&&Rank2[Sa2[i]+c]==Rank2[Sa2[i-1]+c]?p:++p);
    		memcpy(Rank2,C,n<<2);
    	}
    	for(i=0,j=0,k=0;i<n;++i){
    		if(Rank2[i]==0) continue;
    		for(k=(k?k-1:0),j=Sa2[Rank2[i]-1];str[i+k]==str[j+k];++k);
    		Height2[Rank2[i]]=k;
    	}
    	for(i=n-1;i>=0;--i){
    		ST2[i][0]=Height2[i];
    		for(j=0;j<18;++j){
    			if(i+(1<<j)<n) ST2[i][j+1]=min(ST2[i][j],ST2[i+(1<<j)][j]);
    			else ST2[i][j+1]=ST2[i][j];
    		}
    	}
    }
    void Da(){
    	da();
    	reverse(str,str+n-1);
    	da2();
    	reverse(str,str+n-1);
    }
    inline int Lcp(int a,int b){
    	a=Rank[a]; b=Rank[b];
    	if(a>b) swap(a,b);
    	a++;
    	return min(ST[a][Table[b-a+1]],ST[b-(1<<Table[b-a+1])+1][Table[b-a+1]]);
    }
    inline int Lcs(int a,int b){
    	a=n-2-a, b=n-2-b;
    	a=Rank2[a]; b=Rank2[b];
    	if(a>b) swap(a,b);
    	a++;
    	return min(ST2[a][Table[b-a+1]],ST2[b-(1<<Table[b-a+1])+1][Table[b-a+1]]);
    }
    int QueryMin(int l,int r){
        //查询[l,r]内Rank最小的下标
    	int t=Table[r-l+1];
    	return Rank[st[l][t]]<Rank[st[r-(1<<t)+1][t]]?st[l][t]:st[r-(1<<t)+1][t];
    }
    bool Judge(int s1,int t1,int s2,int t2){
        //当答案相同时比较字典序
    	int z=Lcp(s1,s2);
    	for(int i=s1+z,j=s2+z;i<=t1&&j<=t2;++i,++j)
    	    //其实这个for循环最多执行一次,因为已经减去了LCP,str[i]一定不等于str[j]。
    		if(str[i]!=str[j]) return str[i]<str[j];
    	return t1-s1<t2-s2;
    }
    int main(){
    	for(int i=1,k=0;i<=100000;i<<=1,++k){
    		for(int j=i;j<(i<<1)&&j<=100000;++j) Table[j]=k;
    	}
    	int pp=0;
    	while(scanf("%s",str),str[0]!='#'){
    	    ++pp; printf("Case %d: ",pp);
    		n=strlen(str);
    		int l,i,x,y,z,ans=1,fr,to,p,q,minn=0x7f7f7f7f;
    		for(int i=0;i<n;++i) if(str[i]<minn) minn=str[i],fr=i,to=i;
    		str[n++]='$'; str[n]=0;
    		Da();
    		for(l=1;(l<<1)<=n;++l){
    			for(i=0;i+l<n;i+=l*(y/l+1)){
    				x=i+l;
    				y=Lcp(i,x); z=Lcs(i,x);
    				if(y+z<l) continue;
    				p=y+z+l-1;
    				p/=l;
    				q=QueryMin(i-z+1,x+y-p*l);
    				if(p>ans) ans=p,fr=q,to=q+p*l-1;
    				else if(p==ans&&Judge(q,q+p*l-1,fr,to))
    				    fr=q,to=q+p*l-1;
    			}
    		}
    		for(int i=fr;i<=to;++i) putchar(str[i]);
    		puts("");
    	}
    	fcl;
    }
    
    
  • 相关阅读:
    eclipse导入项目后出现红色叉号
    java.lang.ArithmeticException: Non-terminating decimal expansion
    IDEA 2020 注册码
    最新调查:游戏女性玩家群体达3亿!这意味着什么,嘻嘻......
    Linux安装配置Nginx服务器
    mysql/mariadb 远程登陆
    redis 集群 新增 master 节点 与 slave 挂靠命令
    RedisCluster 集群 之 Ruby 安装
    Linux CentOS 7 关闭防火墙
    springcloud 与 springboot 对应关系
  • 原文地址:https://www.cnblogs.com/kito/p/7111414.html
Copyright © 2011-2022 走看看