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;
}