【題解】CF808G Anthem of Berland

題意

傳送門 luogu

題解

dp與kmp的巧妙結合。

設文本串s長度爲nn,模式串t長度爲mm。題面中赤裸裸地告訴你nm107nm\leq 10^7,這就擺明了複雜度應該在O(nm)O(nm)這個級別,這個nn的複雜度肯定是掃一遍s,至於mm,可以猜想是對於s的每個位置進行暴力的匹配。

我們可以考慮用dp來解決這個問題。設fif_i表示t在s的前ii個位置最大的出現次數。那麼如果一個位置想從之前的位置轉移過來,就必須滿足t能在這個位置與s匹配,這一部分可以O(m)O(m)暴力判斷。

具體怎麼轉移呢?首先很明顯可以直接從fimf_{i-m}轉移過來,表示im+1ii-m+1\sim i這段放一個完整的t。

但是這還不夠,因爲有可能在這個位置之前連續而重疊地放了好幾個t,也就意味着新放進去地這個t並不是完整地,而是和上一個t的後綴重疊構成的。那麼這就需要滿足t的一段後綴和一段前綴相等。

這就令我們想到了kmp算法中的next數組。我們可以通過從m開始一直跳next,來保證前綴與後綴相等。

但是又有一個問題,假設我們現在長度爲kk的前後綴相等,我們卻不能直接從fi(mk)f_{i-(m-k)}轉移,因爲ff的定義並不能保證fi(mk)f_{i-(m-k)}這個位置上一定放了t。

所以我們再定義一個gig_i,表示s的前ii個位置,強制最後放一個t的最大出現次數。那麼這樣我們上面的情況就可以通過gg之間的轉移來實現了。即gi=max{gi(mk)+1,gi}g_i=\max\{g_{i-(m-k)}+1,g_i\},一直跳next更新即可。

轉移完gg之後,我們再令fi=max{fi1,gi}f_i=\max\{f_{i-1}, g_i\},也就是考慮放和不放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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章