[CF126B]Password 多解
作者:互联网
/*法1:通过 exkmp,求得 z 数组 错误复杂度方法: 1.枚举每一个后缀,通过 Z 可以求出与前缀的 LCP 然后去串里面暴力找子串。 2.枚举每一个中间串,通过 Z 可以求出与前缀的 LCP ,将这些长度暴力染色,遇到后缀就判一下。 正解: 通过exkmp[i]+i==len判断出这个串既是前缀又是后缀 maxn记录当前最大的exkmp,如果说我们已经找到了一个后缀前缀, 并且在i前面的最大的exkmp[i]是要比n-i长的,说明在中间至少也出现了一个相同的子串 */ #include<bits/stdc++.h> using namespace std; typedef unsigned long long ull; const int N = 1000005; char s[N]; int exkmp[N],len; void Exkmp(){ exkmp[1]=len; int l=0,r=0; for(int i=2;i<=len;i++){ if(i<=r)exkmp[i]=min(exkmp[i-l+1],r-i+1); while(s[i+exkmp[i]]==s[exkmp[i]+1]&&i+exkmp[i]<=len) exkmp[i]++; if(i+exkmp[i]-1>r)r=i+exkmp[i]-1,l=i; } } int maxn=-10,pos; int main(){ scanf("%s",s+1); len=strlen(s+1); Exkmp(); for(int i=2;i<=len;i++){ if(i+exkmp[i]-1==len&&maxn>=len-i+1){pos=i;break;} maxn=max(maxn,exkmp[i]); } if(!pos)puts("Just a legend"); else for(int i=1;i<=len-pos+1;i++)printf("%c",s[i]); return 0; }/* 法2:kmp 找一个既是S的前缀又是S的后缀同时又在S中间出现过的最长子串 其实就是找一个从0开始的一个子串使得它的nxt>=nxt[len] 从2枚举到n-1这样可以保证既不是前缀也不是后缀, 如果next[i]=next[n]那么就找到了最大的子串 注意如果 next[n]>max(next[1...n-1]) 时显然无法找到 #include<bits/stdc++.h> using namespace std; const int N = 1e6+6; char a[N]; int kmp[N],n,maxx=0; void getkmp(){ kmp[1]=0; for(int i=2,j=0;i<=n;i++){ while(a[i]!=a[j+1]&&j)j=kmp[j]; if(a[i]==a[j+1])j++; kmp[i]=j; if(i!=n)maxx=max(maxx,kmp[i]); } } int main(){ scanf("%s",a+1); n=strlen(a+1); getkmp(); int x=kmp[n]; if(!x)puts("Just a legend"); else{ while(x>maxx)x=kmp[x]; if(!x)puts("Just a legend"); for(int i=2;i<n;i++){ if(x==kmp[i]){ for(int j=1;j<=kmp[i];j++) printf("%c",a[i]); puts(""); return 0; } } } } *//* 法3:字符串hash神教 思路:字符串,于是想到hash。发现时间复杂度有问题,考虑优化 优化字符串方式在于合理利用残余的信息,如kmp 发现二分性质,解决 性质:对于一个前缀 T,如果存在一个不在最左边也不在最右边的子串 U, 那么一个比 T 短的前缀也一定能找到对应的 U' 这条性质为二分奠定了基础 做法:1.O(n) 找出所有合法的前缀 T(有对应的后缀), 2.二分,判断是否存在 U O(n) 判,总复杂度 O(nlogn) #include<bits/stdc++.h> using namespace std; const int N = 1e6+6; const int mod = 998244353 char s[N]; int hsh[N],p[N],n; vector<int>v; int calc(int l,int r){return hsh[r]-hsh[l-1]*p[r-l+1];} bool check(int len,int val){ for(int i=2;i+len-1<n;i++){ if(val==calc(i,i+len-1)) return ture; } return false; } int main(){ scanf("%s",s+1); n=strlen(s+1); p[0]=1; for(int i=1;i<=n;i++) p[i]=p[i-1]*131%mod, hsh[i]=(hsh[i-1]*131%mod+s[i])%mod; for(int i=1;i<=n-2;i++){ if(calc(1,i)==calc(n-i+1,n)) v.push_back(i); } int l=0,r=(int)v.size()-1,ans=0,mid; while(l<=r){ mid=(l+r)>>1; if(check(v[mid],calc(1,v[mid]))) ans=mid,l=mid+1; else r=mid-1; } if(!ans)puts("Just a legend"); else for(int i=1;i<=ans;i++)printf("%c",s[i]); return 0; } *//* 法4:正反两次kmp 我们可以发现一个切入点: 对于我们找到的一个满足条件的字符串 P(假设它在S中的起始和终点坐标分别为l,r) 那么我们发现(设 |S|=n): P 既是 S[1...r] 的前缀,也是 S[1...r] 的后缀 P 既是 S[l...n] 的前缀,也是 S[l...n] 的后缀 具体:https://www.luogu.com.cn/blog/Sangber/solution-cf126b *//* 法5:exkmp + hash 乱搞 exkmp求出自匹配数组,然后 hash 得到所有后缀存入map, 枚举 exkmp,map判断是否存在相应后缀 (以下是 0 分代码,还在debug) #include<bits/stdc++.h> using namespace std; typedef unsigned long long ull; const int N = 1000005; char s[N]; int exkmp[N],len; ull hsh[N],p[N]; map<ull,bool>mp; bool debug=0; void Exkmp(){ exkmp[1]=len; int l=0,r=0; for(int i=2;i<=len;i++){ if(i<=r)exkmp[i]=min(exkmp[i-l+1],r-i+1); while(s[i+exkmp[i]]==s[exkmp[i]+1]&&i+exkmp[i]<=len) exkmp[i]++; if(i+exkmp[i]-1>r)r=i+exkmp[i]-1,l=i; } } int main(){ scanf("%s",s+1); len=strlen(s+1); p[0]=1; for(int i=1;i<=len;i++){ p[i]=p[i-1]*131; hsh[i]=hsh[i-1]*131+s[i]; } Exkmp(); if(debug){ printf("exkmp:\n"); for(int i=1;i<=len;i++)printf("i:%d exkmp:%d\n",i,exkmp[i]); printf("hash:\n"); } for(int i=1;i<=len;i++){ mp[hsh[len]-hsh[i-1]*p[len-i+1]]=1; if(debug)printf("i:%d hsh:%llu\n",i,hsh[len]-hsh[i-1]*p[len-i+1]); } if(debug)printf("compare:\n"); int ans=0,ansp; for(int i=2;i<len;i++){ if(i+exkmp[i]-1==len||!exkmp[i])continue; if(mp[hsh[exkmp[i]]]&&exkmp[i]>ans) if(debug)printf("i:%d exkmp[i]:%d hah:%llu\n",i,exkmp[i],hsh[exkmp[i]]), ans=exkmp[i],ansp=i; } if(!ans)puts("Just a legend"); else for(int i=ansp;i<ansp+ans;i++)printf("%c",s[i]); return 0; } */
标签:exkmp,const,CF126B,hsh,mid,len,多解,int,Password 来源: https://www.cnblogs.com/wyb-sen/p/14355880.html