【题解】CF808G Anthem of Berland

题意

传送门 luogu

题解

dp与kmp的巧妙结合。

设文本串s长度为 n n n,模式串t长度为 m m m。题面中赤裸裸地告诉你 n m ≤ 1 0 7 nm\leq 10^7 nm107,这就摆明了复杂度应该在 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} fim转移过来,表示 i − m + 1 ∼ i i-m+1\sim i im+1i这段放一个完整的t。

但是这还不够,因为有可能在这个位置之前连续而重叠地放了好几个t,也就意味着新放进去地这个t并不是完整地,而是和上一个t的后缀重叠构成的。那么这就需要满足t的一段后缀和一段前缀相等。

这就令我们想到了kmp算法中的next数组。我们可以通过从m开始一直跳next,来保证前缀与后缀相等。

但是又有一个问题,假设我们现在长度为 k k k的前后缀相等,我们却不能直接从 f i − ( m − k ) f_{i-(m-k)} fi(mk)转移,因为 f f f的定义并不能保证 f i − ( m − k ) f_{i-(m-k)} fi(mk)这个位置上一定放了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(mk)+1,gi},一直跳next更新即可。

转移完 g g g之后,我们再令 f i = max ⁡ { f i − 1 , g i } f_i=\max\{f_{i-1}, g_i\} fi=max{fi1,gi},也就是考虑放和不放t两种情况。

代码

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

你可能感兴趣的:(题解,DP)