题目大意:
题目链接:
USACO:http://train.usaco.org/usacoprob2?a=dLE0hVDUyv1&S=prefix
洛谷:https://www.luogu.org/problemnew/show/P1470
给出多个子串和一个字符串,求该字符串的前多少位可以完全被子串覆盖掉。
思路:
很多人都说用
D
P
DP
DP和搜索,但是我怎么看都是
K
M
P
KMP
KMP。
我们可以在
O
(
n
)
O(n)
O(n)的时间复杂度内求出一个元素在
S
S
S序列里的位置,那么可以用前缀和的思想,用一个数组记录答案,找到一个位置后,将头的位置
i
i
i的答案
a
n
s
i
+
+
ans_i++
ansi++,尾的位置
j
j
j的答案
a
n
s
j
+
+
ans_j++
ansj++。对所有元素进行一边改操作,时间复杂度
O
(
n
m
)
O(nm)
O(nm),其中
m
m
m表示元素个数。
然后以
a
n
s
ans
ans跑一遍前缀和,此时如果
a
n
s
ans
ans前
k
k
k个数大于
0
0
0,答案就是
k
k
k。
代码:
#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;
}