KMP算法
扩展KMP(Z函数) O(N)
#include<iostream> #include<cstring> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=2e7+5; char t[maxn],s[maxn]; int z[maxn],p[maxn]; void get_z(char* s,int n){ z[1]=n; for(int i=2,l,r=0;i<=n;i++){ if(i<=r) z[i]=min(z[i-l+1],r-i+1); //两种情况里面取较小的值 while(s[i+z[i]]==s[1+z[i]]) z[i]++; //就可以暴力匹配 if(i+z[i]-1>r) l=i,r=i+z[i]-1; //更新box } } void get_p(char *s,int n,char *t,int m){ for(int i=1,l,r=0;i<=m;i++){ //利用s的z数组去更新p数组 if(i<=r) p[i]=min(z[i-l+1],r-i+1); while(1+p[i]<=n&&i+p[i]<=m&&s[1+p[i]]==t[i+p[i]]) p[i]++; if(i+p[i]-1>r) l=i,r=i+p[i]-1; } } int main(){ scanf("%s%s",t+1,s+1); int m=strlen(t+1); int n=strlen(s+1); get_z(s,n); get_p(s,n,t,m); // for(int i=1;i<=n;i++) cout<<z[i]<<" "; // cout<<endl; // for(int i=1;i<=m;i++) cout<<p[i]<<" "; long long ans1=0,ans2=0; for(int i=1;i<=n;i++){ ans1^=1LL*i*(z[i]+1); } for(int i=1;i<=m;i++){ ans2^=1LL*i*(p[i]+1); } cout<<ans1<<endl<<ans2; return 0; }
1465:【例题1】剪花布条
kmp的典型例题
因为找到了花布条不能重复,所以记得记录上一次找到的结束位置,当前位置减去结束位置大于需要的长度才算一个新的
在每一次匹配中,这个串在S中的起点是i+1-m,终点是i
1466:【例题2】Power Strings
这个属于KMP解决最小循环节问题】
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1e6+10; const int INF=0x3fffffff; typedef long long LL; //跟hash里面的一道题一模一样 //询问每个字符串最多是由多少个相同的子字符串重复连接而成的 求最短重复子序列 //这道题试问我们一个串有几个循环节。循环节就是指相等的(真)前缀和(真)后缀的个数。我们知道, //kmp过程中的next[i]是这个意义:0-i-1位中相等的真前后缀个数。那么next[len]就是指0-len-1位中相等的真前后缀个数。 char s[maxn]; int next[maxn],len; void getnext(){ int j=-1; int i=0; next[0]=-1; while(i<len){ if(j==-1||s[i]==s[j]){ i++;j++; next[i]=j; } else j=next[j]; } } int main(){ while(1){ scanf("%s",s); if(s[0]=='.') break; memset(next,0,sizeof(next)); len=strlen(s); getnext(); if(len%(len-next[len])==0) printf("%d\n",len/(len-next[len])); else printf("1\n"); } return 0; }
1467:Radio Transmission
给你一个字符串,它是由某个字符串不断自我连接形成的。但是这个字符串是不确定的,现在只想知道它的最短长度是多少。
KMP解决最小循环节问题
https://blog.csdn.net/yyhs_wsf/article/details/84302201 看图理解
最小循环节就是n-next[n]
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1e6+10; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; //处理循环节的问题 //结论是: n-next[n] //看图好理解:https://www.sogou.com/link?url=hedJjaC291OB0PrGj_c3jHF3JVBxwgdKrV4_oh6vctquSdKpCMIsLs1-z3wO5pvEktsLTQmBnHo. char s[maxn]; int len,next[maxn]; int main(){ scanf("%d %s",&len,s); next[0]=-1; int i=0,j=-1; while(i<len){ if(j==-1||s[i]==s[j]){ next[++i]=++j; } else j=next[j]; } printf("%d\n",len-next[len]); return 0; }
1468:OKR-Periods of Words
KMP只能求出每个前缀串的最长匹配长度,如果要求出最短匹配长度,我们可以一直递推next[i],next[next[i]]...,直到为0. 熟悉的KMP本质的人都应该知道为什么,这里举一个例子。
在S中,next[8]=5,而next[5]=2,next[2]=0了,所以next[5]=2就是8的最短匹配长度,将8-2累计到答案中即可。
最后,类似求next时的递推方法,我们可以递推short来提高效率。比如在上例中,我们得到short[8]=2后,就直接将next[8]修改为2,
这样8以后的数字如果递推到8了就可以直接跳到next[2]=0,而不用跳到next[5]这里。
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1e6+10; const int INF=0x3fffffff; typedef long long LL; //这道题题目理解有点困难,可以画图理解 //next[]数组求的是最大匹配前后缀,但是这道题根据题目的意思,想求的是最小匹配前后缀,才能使整个更长 /* KMP只能求出每个前缀串的最长匹配长度,如果要求出最短匹配长度,我们可以一直递推next[i],next[next[i]]...,直到为0. 熟悉的KMP本质的人都应该知道为什么,这里举一个例子。 在S中,next[8]=5,而next[5]=2,next[2]=0了,所以next[5]=2就是8的最短匹配长度,将8-2累计到答案中即可。 最后,类似求next时的递推方法,我们可以递推short来提高效率。比如在上例中,我们得到short[8]=2后,就直接将next[8]修改为2, 这样8以后的数字如果递推到8了就可以直接跳到next[2]=0,而不用跳到next[5]这里。 */ char s[maxn]; int nxt[maxn]; int len; unsigned long long ans=0; int main(){ scanf("%d",&len); scanf("%s",s+1); int j=0; nxt[0]=nxt[1]=0; for(int i=2;i<=len;i++){ while(j&&(s[i]!=s[j+1])) j=nxt[j]; j+=(s[i]==s[j+1]); nxt[i]=j; } for(int i=2;i<=len;i++){ if(nxt[nxt[i]]) nxt[i]=nxt[nxt[i]]; } for(int i=2;i<=len;i++) if(nxt[i]) ans+=i-nxt[i]; printf("%lld\n",ans); return 0; }
1469:似乎在梦中见过的样子
KMP类似NOI2014动物园的算法一样。枚举左端点,对于每个右端点处理出以右端点为结尾最大长度使得从左端点开始的前缀等于以右端点结束的后缀,即next数组。然后一直往前跳,直到长度小于子串长度的一半为止。如果这个时候长度满足题目要求,就累加
#include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<iostream> #include<algorithm> #define maxn 20010 using namespace std; int ne[maxn]; char a[maxn]; int n,m,ans; void pre(){ for(int k=1;k<=n-(m*2);k++){ //枚举左端点 int j=k-1; for(int i=k;i<=n;i++) ne[i]=k-1; for(int i=k+1;i<=n;i++){ while(j>k-1&&a[i]!=a[j+1]) j=ne[j]; if(a[i]==a[j+1]) j++; ne[i]=j; int t=j; while(t-k+1>=i-t) t=ne[t]; //保证中间有空 if(t-k+1>=m) ans++; } } } int main() { scanf("%s",a+1); scanf("%d",&m); n=strlen(a+1); pre(); printf("%d\n",ans); return 0; }
1470:Censoring
题解:
https://www.luogu.com.cn/problemnew/solution/P3121
栈+AC自动机
开两个栈,一个记录当前节点扫到AC自动机的哪个地方了,一个记录当然栈内合法的字符,每当发现一个能删去的字符串的时候,只需要把两个栈都减去字符串长度即可,把now修改一下,继续往后扫即可,最后输出第二个栈即可。
这道题有挺多方法,这个只是比较好想
一本通上面通不过无语
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<queue> using namespace std; char s[100001],ori[100001]; int n,tot,w,top; int trie[100001][26],fail[100001],heap[100001],sign[100001]; int isend[100001]; void insert(char *s){ int now=0,len=strlen(s); for(int i=0;i<len;i++){ int x=s[i]-'a'; if(!trie[now][x])trie[now][x]=++tot; now=trie[now][x]; } isend[now]=len; } void makefail(){ queue<int> q; for(int i=0;i<26;i++) if(trie[0][i])q.push(trie[0][i]); while(!q.empty()){ int now=q.front();q.pop(); for(int i=0;i<26;i++){ if(!trie[now][i]){ trie[now][i]=trie[fail[now]][i]; continue; } fail[trie[now][i]]=trie[fail[now]][i]; q.push(trie[now][i]); } } } void solve(char *s){ int now=0,len=strlen(s),i=0; w=0; while(i<len){ int x=s[i]-'a'; now=trie[now][x]; sign[++top]=now; heap[top]=i; if(isend[now]){ top-=isend[now]; if(!top) now=0; else now=sign[top]; } i++; } } int main() { scanf("%s",s); scanf("%d",&n); int len=strlen(s); for(int i=1;i<=n;i++){ scanf("%s",ori); insert(ori); } makefail(); solve(s); for(int i=1;i<=top;i++) printf("%c",s[heap[i]]); return 0; }