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 ",len/(len-next[len])); else printf("1 "); } 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 ",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 ",ans); return 0; }
1469:似乎在梦中见过的样子
先理解一下暴力的解法,然后再开优化,这道题暴力好像过不了
#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=1.5e4+10; const int INF=0x3fffffff; typedef long long LL; //暴力的做法,在一本通上面会超时 /* 你就枚举一个左端点。。 然后对于这个左端点,我们对后面的字符串跑一次KMP 然后呢,再枚举一个右端点,判断行不行。。 其实呢就还是按KMP的next指针往前跳 然后你可以得到一段东西是可以的 然后,你还要保证中间有一个B的空间,于是要继续往前跳。。 跳完,你还要使这个区间>=k 于是就可以了。。 */ int k,nxt[maxn]; char s[maxn]; int ans=0; void js(char *ss){ int j=0; nxt[1]=0; for(int i=2;ss[i];i++){ while(j&&ss[i]!=ss[j+1]) j=nxt[j]; if(ss[i]==ss[j+1]) j++; nxt[i]=j; } j=nxt[2*k+1]-1; //至少要保证这个长度 for(int i=2;ss[i];i++){ while(j&&ss[i]!=ss[j+1]) j=nxt[j]; if(ss[i]==ss[j+1]) j++; while(j*2>=i) j=nxt[j]; //不断往前走,保证存在B if(j>=k) ans++; } } int main(){ scanf("%s",s+1); scanf("%d",&k); for(int i=0;s[i+1];i++) js(s+i); //必须要从0开始 printf("%d ",ans); return 0; } #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=20000; const int INF=0x3fffffff; typedef long long LL; /* 基本地:由于 O(n2) 就行,枚举子串左端点,计算每个子串的特征向量,对符合要求的累加。 特别地:KMP中计算的特征向量 nxt[i] 表示的是以i为右端点,题目中A串的最小长度,由于题目对A的长度做了限制。于是,我们另开一个数组 f, 记录满足A长度条件的最小长度 j,若 j 同时满足 2*j<i 则累加。 收获:nxt数组本质上可以看作父节点表示法表示的一颗树,我们自根向下延申。要记录的是中间的满足条件的点。(之前最大循环节那道题 f 数组记录的就是根节点的值, 不过我没意识到是棵树。。) */ int k,n; LL ans=0; int nxt[maxn],f[maxn]; //,f记录满足A长度条件的最小长度 j,若 j 同时满足 2*j<i 则累加。 char s[maxn]; void get_nxt(int x){ nxt[1]=0; int j=0; for(int i=2;i<=n-x;i++){ while(j&&s[i+x]!=s[x+j+1]) j=nxt[j]; if(s[j+x+1]==s[i+x]) j++; nxt[i]=j; //f[]记录的其实是位置,能够满足next值大于k,去得到的最小的i的位置, if(f[j]) f[i]=f[j]; else if(j>=k) f[i]=j; else f[i]=0; if(f[i]&&(f[i]<<1)<i) ans++; } } int main(){ scanf("%s %d",s+1,&k); n=strlen(s+1); for(int i=1;i<=n;i++) get_nxt(i-1); printf("%lld ",ans); return 0; }
1470:Censoring
题解:
https://www.luogu.com.cn/problemnew/solution/P3121
#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; }