【USACO2.3.1】【洛谷P1470】最长前缀【KMP】

题目大意:

题目链接:

USACO:http://train.usaco.org/usacoprob2?a=dLE0hVDUyv1&S=prefix
洛谷:https://www.luogu.org/problemnew/show/P1470

给出多个子串和一个字符串,求该字符串的前多少位可以完全被子串覆盖掉。


思路:

很多人都说用DPDP和搜索,但是我怎么看都是KMPKMP
我们可以在O(n)O(n)的时间复杂度内求出一个元素在SS序列里的位置,那么可以用前缀和的思想,用一个数组记录答案,找到一个位置后,将头的位置ii的答案ansi++ans_i++,尾的位置jj的答案ansj++ans_j++。对所有元素进行一边改操作,时间复杂度O(nm)O(nm),其中mm表示元素个数。
然后以ansans跑一遍前缀和,此时如果ansanskk个数大于00,答案就是kk


代码:

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;

const int M=210;
const int N=200100;
int n,m,j,sum=1,next[20],ans[N];
char a[N],b[M][20],c[M];

int main()
{
    while (cin>>b[sum]+1&&b[sum][1]!='.') sum++;
    while (cin>>c)  //太菜不知道有什么更好的读入方法
        for (int i=0;i<strlen(c);i++)
            a[++n]=c[i];
    for (int k=1;k<sum;k++)
    {
        memset(next,0,sizeof(next));
        m=strlen(b[k]+1);
        j=0;
        next[1]=0; 
        for (int i=1;i<m;i++)  //求next
        {
            while (j&&b[k][j+1]!=b[k][i+1]) j=next[j];
            if (b[k][j+1]==b[k][i+1]) j++;
            next[i+1]=j;
        }
        j=0;
        for (int i=0;i<n;i++)  //KMP
        {
            while (j&&b[k][j+1]!=a[i+1]) j=next[j];
            if (b[k][j+1]==a[i+1]) j++;
            if (j==m)
            {
                ans[i+2]--;
                ans[i-m+2]++;
                j=next[j];
            }
        }
    }
    for (int i=1;i<=n;i++)
        ans[i]+=ans[i-1];  //跑一遍前缀和
    for (int i=1;i<=n;i++)
        if (ans[i]<=0)
        {
            printf("%d\n",i-1);
            return 0;
        }
    printf("%d\n",n);
    return 0;
}
posted @ 2018-11-25 08:45  全OI最菜  阅读(144)  评论(0编辑  收藏  举报