KMP题集-专注Next数组

本文探讨了KMP算法在实际问题中的应用,如矩阵最小覆盖、循环子串长度求解、字符串匹配次数统计等。通过理解Next数组的精髓,作者展示了如何巧妙地利用KMP技巧解决复杂字符串操作,包括矩阵组合、小数循环节识别和重复前缀计数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值