kmp的关键之处还是在Next数组上
Next存的是前缀和后缀相等的长度
基础的模板题就不上啦,来几个应用题目~~~
POJ-2185
给出一个大矩阵,求最小覆盖矩阵,大矩阵可由这个小矩阵拼成。(就如同拼磁砖,允许最后有残缺)
emmm~~二维滴,本来想
KMP的next求出每行的最小循环子串长度,然后求这些长度的公倍数,作为宽(若大于col,则为col)。
然后用KMP的next求出每列的最小循环子串长度,然后求出这些长度的公倍数,作为长(若大于row,则为row)。
虽然我最后也过了,but 有反例的
2 8
ABCDEFAB
AAAABAAA
答案是12 ,所以很多人可能会想对于单行我们不一定要选最短循环节,所以需要把所有的循环节求出,取lcm最小的
当然没问题啦
but 有更骚的操作哦,那就是直接用kmp的Next数组,在比较的时候不是比较单个字符而是比较两行/两列
真实是个好题~(奈何数据太弱,错误解法也能AC)
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn = 1e4 + 10;
int n,m,t,k,ans;
char s[maxn][100];
int Next[maxn];
bool cmp(int x,int y,int f)
{
if(f)
{
for(int i = 0;i < m; ++i) ///比较行
if(s[x][i] != s[y][i]) return 0;
return 1;
}
for(int i = 0;i < ans; ++i) ///比较列
if(s[i][x] != s[i][y]) return 0;
return 1;
}
void getNext(int x)
{
int i = 0,j = Next[0] = -1,k;
if(!x) k = m;
else k = n;
while(i < k)
{
if(j == -1 || cmp(i,j,x))
Next[++i] = ++j;
else j = Next[j];
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 0;i < n; ++i)
scanf("%s",s[i]);
getNext(1);
ans = n - Next[n];
getNext(0);
ans *= (m - Next[m]);
printf("%d\n",ans);
return 0;
}
HDU-6740
这是ccpc2019秦皇岛的题~~~
题意不多赘述了,这题的主要想法就是将小数部分倒过来求Next数组
倒叙过来之后,开头的部分一定是循环节,枚举前缀求循环节就可以啦
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e7 + 10;
typedef long long ll;
int Next[maxn],T,n,m,t;
char s[maxn],sa[maxn];
ll a,b,ans;
void getNext()
{
int i = 0,j = Next[0] = -1;
while(s[i])
{
if(j == -1 || s[i] == s[j])
Next[++i] = ++j;
else j = Next[j];
}
}
int main()
{
while(~scanf("%lld%lld",&a,&b))
{
ans = -1e18;
scanf("%s",sa);
int k = 0;
t = 0;
while(sa[k] != '.') k++;
for(int i = strlen(sa) - 1; i > k; --i)
s[t++] = sa[i];
s[t] = '\0';
m = strlen(s);
getNext();
//cout<<s<<endl;
for(int i = 1;i <= m; ++i)
ans = max(ans,a * i - b * (i - Next[i]));
printf("%lld\n",ans);
}
}
POJ-3461
s在t中出现的次数
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<string>
using namespace std;
int Next[1000010],T,n,m,ans;
char s[1000010],t[1000010];
void getNext()
{
int i = 0,j = Next[0] = -1;
while(s[i])
{
if(j == -1 || s[i] == s[j])
Next[++i] = ++j;
else j = Next[j];
}
}
void kmp()
{
getNext();
int i = 0 , j = 0;
while(i < n)
{
while(j != -1 && t[i] != s[j]) j = Next[j];
i++; j++;
if(j >= m)
{
ans++;
j = Next[j];
}
}
}
int main()
{
scanf("%d",&T);
while(T--)
{
ans = 0;
scanf("%s",s);
scanf("%s",t);
m = strlen(s);
n = strlen(t);
kmp();
printf("%d\n",ans);
}
}
POJ-2406
HDU-3746
HDU-1358
HDU-2087
POJ-2752
问前缀和后缀相同的字符串长度可以为多少?
#include<stack>
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = 1e6 + 10;
char a[maxn];
int Next[maxn],n,len,T;
stack<int>s;
void getnext()
{
int i = 0,j = Next[0] = -1;
while(i < n)
{
if(j == -1 || a[i] == a[j]) Next[++i] = ++j;
else j = Next[j];
}
}
int main()
{
while(~scanf("%s",a))
{
n = strlen(a);
getnext();
s.push(n);
bool flag = 0;
int j = Next[n];
while(j && j != -1)
{
s.push(j);
j = Next[j];
}
while(!s.empty())
{
if(flag) printf(" ");
flag = 1;
printf("%d",s.top());
s.pop();
}
printf("\n");
}
return 0;
}
POJ-3080
HDU-2328
HDU-3336
问所有的前缀在字符串中一共出现了几次
做KMP后枚举每一个终点,对于它的next[len]进行递归寻找,直到他的next[len]为0,那么递归的次数也就是它的前缀出现的次数(dp[i] = dp[next[i]] +1 )
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=200010;
const int mod=10007;
int net[maxn],dp[maxn];
char str[maxn];
void getnext(int len)
{
int i=0,j=-1;
net[0]=-1;
while(i<len)
{
if(j==-1 || str[i]==str[j])
{
i++;j++;
net[i]=j;
}
else j=net[j];
}
}
int main()
{
int t,n;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
scanf("%s",str);
getnext(n);
memset(dp,0,sizeof(dp));
int sum=0;
for(int i=1;i<=n;i++)
{
dp[i]=(dp[net[i]]+1);
sum=(sum+dp[i])%mod;
}
printf("%d\n",sum);
}
return 0;
}