KMP的循环节问题总结 ###K
len=i-next[i] 等价于 以i为终点的最小循环节长度
假设 总长为n 如果len能整除n 说明周期为n/len
否则 差几个就补几个就能达到一个周期 即 ans= (len-n%len)%len
题目链接:https://vjudge.net/contest/394060#problem/J
题意:给一段字符串 问小数部分后的 a*p-b*l 最大为多少, a b给定, p是该循环节的总长度,l是循环节的一个周期的长度
思路:求循环节 考虑kmp 倒序kmp求出next数组 i-next[i] 即为循环节的一个周期长度 即长度减去公共前后缀的长度= 一个周期的长度
详细理解同第四题
1 #include2 using namespace std; 3 #define ll long long 4 #define pb push_back 5 const int maxn =1e7+10; 6 const int mod=1e9+7; 7 8 9 char t[maxn]; 10 char s[maxn]; 11 int nxt[maxn]; 12 13 14 15 int main() 16 { 17 ios::sync_with_stdio(false); 18 cin.tie(0); 19 ll a,b; 20 while(cin>>a>>b) 21 { 22 cin>>(t+1); 23 int n=strlen(t+1); 24 int cnt=0; 25 for(int i=n;i>=1;i--) 26 { 27 if(t[i]=='.') 28 break; 29 s[++cnt]=t[i]; 30 } 31 32 int j=0; 33 for(int i=2;i<=cnt;i++) 34 { 35 while(j&&s[i]!=s[j+1]) 36 j=nxt[j]; 37 if(s[i]==s[j+1]) j++; 38 nxt[i]=j; 39 } 40 /*for(int i=1;i<=cnt;i++) 41 cout< */ 42 ll ans=-1e18; 43 for(int i=1;i<=cnt;i++) 44 { 45 ans=max(ans,a*i-b*(i-nxt[i])); 46 } 47 cout< \n'; 48 49 50 } 51 52 53 54 55 56 }'
题目链接:https://vjudge.net/problem/HDU-3746
题意:给定一串珠子 问最少增加多少个珠子使得 这串珠子的循环节个数>=2
思路: len= i-nxt[i] 如果len==n 那么就ans=n 否则 ans= (len-n%len)%len
所以kmp求出next数组后 判断n-next[n] 即可
1 #include2 using namespace std; 3 const int maxn=1e5+10; 4 const int mod=1e9+7; 5 #define ll long long 6 #define ull unsigned long long 7 #define pi pair 8 #define fi first 9 #define sc second 10 #define pb push_back 11 12 char s[maxn]; 13 int nxt[maxn]; 14 int n; 15 16 void getnxt() 17 { 18 for(int i=2,j=0;i<=n;i++) 19 { 20 while(j&&s[i]!=s[j+1]) j=nxt[j]; 21 if(s[i]==s[j+1]) j++; 22 nxt[i]=j; 23 } 24 } 25 26 27 int main() 28 { 29 ios::sync_with_stdio(false); 30 cin.tie(0); 31 int t; 32 cin>>t; 33 while(t--) 34 { 35 cin>>(s+1); 36 n=strlen(s+1); 37 getnxt(); 38 int k=n-nxt[n]; 39 int ans=0; 40 if(k==n) ans=n; 41 else ans=(k-n%k)%k; 42 cout< '\n'; 43 } 44 45 46 47 }
题目链接:https://www.acwing.com/problem/content/143/
题意:找出字符串所有前缀是否存在循环节
思路:kmp求出next数组后 再去枚举前缀的端点即可
kmp已经把 以i 结尾的 nxt[i] 都求出来了
1 #include2 using namespace std; 3 const int maxn=1e6+10; 4 const int mod=1e9+7; 5 #define ll long long 6 #define ull unsigned long long 7 #define pi pair 8 #define fi first 9 #define sc second 10 #define pb push_back 11 12 int n; 13 char s[maxn]; 14 int nxt[maxn]; 15 16 void getnxt() 17 { 18 for(int i=2,j=0;i<=n;i++) 19 { 20 while(j&&s[i]!=s[j+1]) j=nxt[j]; 21 if(s[i]==s[j+1]) j++; 22 nxt[i]=j; 23 } 24 } 25 26 27 28 int main() 29 { 30 ios::sync_with_stdio(0); 31 cin.tie(0); 32 33 int c=0; 34 while(cin>>n,n) 35 { 36 cin>>(s+1); 37 getnxt(); 38 cout<<"Test case #"<<++c<<'\n'; 39 for(int i=1;i<=n;i++) 40 { 41 int len=i-nxt[i]; 42 if(i%len==0&&i/len!=1) 43 { 44 cout<" "<'\n'; 45 } 46 } 47 cout<<'\n'; 48 } 49 50 51 52 53 }
题目链接:https://ac.nowcoder.com/acm/contest/13506/I
题意:删去一段前缀k后 使得剩余的字符串中每个i满足 T[i]=T[i+p] 越界的认为是满足条件
删去的个数为k 要求k+p最小 如果相等即p取最小
思路:枚举删除的前缀, 然后我们需要可以o1 得出剩下的后缀的 循环节长度p (可以看出p就是循环节长度的含义)
所以需要逆序跑一遍kmp 那么i-nxt[i] 即为循环节长度p 因为越界是满足条件的, 所以是非完全循环节即可 同时注意题目要求p>0
逆序看起来 可能是向左增加的循环节 如 X X 1 2 3 1 到的时候可能看起来会向左补 3 2 但其实因为公共前后缀的对称关系
向右补 2 3 是等价关系
1 #include2 using namespace std; 3 const int maxn=1e6+10; 4 const int mod=1e9+7; 5 #define ll long long 6 #define ull unsigned long long 7 #define pi pair 8 #define fi first 9 #define sc second 10 #define pb push_back 11 12 13 int a[maxn]; 14 int nxt[maxn]; 15 int n; 16 17 18 void get() 19 { 20 for(int i=2,j=0;i<=n;i++) 21 { 22 while(j&&a[j+1]!=a[i]) j=nxt[j]; 23 if(a[j+1]==a[i]) j++; 24 nxt[i]=j; 25 } 26 } 27 28 29 30 31 int main() 32 { 33 ios::sync_with_stdio(0); 34 cin.tie(0); 35 cin>>n; 36 for(int i=1;i<=n;i++) cin>>a[i]; 37 reverse(a+1,a+1+n); 38 get(); 39 40 int ans=n,k=n-1,p=1; 41 for(int i=1;i<=n;i++) 42 { 43 int tmpk=n-i; 44 int tmpp=i-nxt[i]; 45 int sum=tmpk+tmpp; 46 if(sum p)) 47 { 48 ans=sum; 49 p=tmpp; 50 k=tmpk; 51 } 52 } 53 cout< " "< '\n'; 54 55 56 57 58 59 60 61 }