题意
题解
dp与kmp的巧妙结合。
设文本串s长度为 n n n,模式串t长度为 m m m。题面中赤裸裸地告诉你 n m ≤ 1 0 7 nm\leq 10^7 nm≤107,这就摆明了复杂度应该在 O ( n m ) O(nm) O(nm)这个级别,这个 n n n的复杂度肯定是扫一遍s,至于 m m m,可以猜想是对于s的每个位置进行暴力的匹配。
我们可以考虑用dp来解决这个问题。设 f i f_i fi表示t在s的前 i i i个位置最大的出现次数。那么如果一个位置想从之前的位置转移过来,就必须满足t能在这个位置与s匹配,这一部分可以 O ( m ) O(m) O(m)暴力判断。
具体怎么转移呢?首先很明显可以直接从 f i − m f_{i-m} fi−m转移过来,表示 i − m + 1 ∼ i i-m+1\sim i i−m+1∼i这段放一个完整的t。
但是这还不够,因为有可能在这个位置之前连续而重叠地放了好几个t,也就意味着新放进去地这个t并不是完整地,而是和上一个t的后缀重叠构成的。那么这就需要满足t的一段后缀和一段前缀相等。
这就令我们想到了kmp算法中的next数组。我们可以通过从m开始一直跳next,来保证前缀与后缀相等。
但是又有一个问题,假设我们现在长度为 k k k的前后缀相等,我们却不能直接从 f i − ( m − k ) f_{i-(m-k)} fi−(m−k)转移,因为 f f f的定义并不能保证 f i − ( m − k ) f_{i-(m-k)} fi−(m−k)这个位置上一定放了t。
所以我们再定义一个 g i g_i gi,表示s的前 i i i个位置,强制最后放一个t的最大出现次数。那么这样我们上面的情况就可以通过 g g g之间的转移来实现了。即 g i = max { g i − ( m − k ) + 1 , g i } g_i=\max\{g_{i-(m-k)}+1,g_i\} gi=max{gi−(m−k)+1,gi},一直跳next更新即可。
转移完 g g g之后,我们再令 f i = max { f i − 1 , g i } f_i=\max\{f_{i-1}, g_i\} fi=max{fi−1,gi},也就是考虑放和不放t两种情况。
代码
#include <bits/stdc++.h>
#define MAX 100005
using namespace std;
char s[MAX], t[MAX];
int n, m;
int Next[MAX], f[MAX], g[MAX];
bool chk(int p){
for(int j = 1; j <= m; j++){
if(s[p-j+1] != t[m-j+1] && s[p-j+1] != '?') return false;
}
return true;
}
int main()
{
scanf("%s%s", s+1, t+1);
n = strlen(s+1), m = strlen(t+1);
for(int i = 2, j = 0; i <= m; i++){
while(j && t[j+1] != t[i]) j = Next[j];
if(t[j+1] == t[i]) j++;
Next[i] = j;
}
for(int i = 1; i <= n; i++){
f[i] = f[i-1];
if(chk(i)){
g[i] = f[i-m]+1;
for(int j = Next[m]; j; j = Next[j]){
g[i] = max(g[i], g[i-(m-j)]+1);
}
f[i] = max(f[i], g[i]);
}
}
cout << f[n] << endl;
return 0;
}