[NOIP2015] 子串substring 題解


【題目描述】

有兩個僅包含小寫英文字母的字符串A和B。現在要從字符串A中取出k個互不重疊的非空子串,然后把這k個子串按照其在字符串A中出現的順序依次連接起來得到一個新的字符串,請問有多少種方案可以使得這個新串與字符串B相等?注意:子串取出的位置不同也認為是不同的方案。

由於答案可能很大,所以這里要求輸出答案對1,000,000,007取模的結果。

【樣例輸入1】

6 3 1

aabaab

aab

【樣例輸出1】

2

【樣例輸入2】

6 3 2

aabaab

aab

【樣例輸出2】

7

【樣例輸入3】

6 3 3

aabaab

aab

【樣例輸出3】

7

【數據規模與約定】

對於100%的數據:1≤n≤1000,1≤m≤200,1≤k≤m。

【解法】

還好吧……一個DP……不過細節比較多,難度不小。

我們令f[i][j][k][0/1]表示A串用了前i個字符,B串已覆蓋前j個字符,目前為止已經選了k個子串,最后的0/1表示A串的這個字符選了沒有(0沒選,1選了)。

為了得出狀態轉移方程,我們分情況討論:

先看f[i][j][k][1](當前位選了),顯然當且僅當a[i]=b[j]的時候它才有意義,否則f[i][j][k][1]=0。

到這個狀態有三種方法:

1. 上一位沒有選,新開一個子串

2. 上一位選了,延續這個子串

3. 上一位選了,但是仍然新開一個子串

因此,我們有

f[i][j][k][1]=f[i-1][j-1][k-1][0]+f[i-1][j-1][k][1]+f[i-1][j-1][k-1][1]。

狀態轉移方程中的三項分別對應上述三種情況。注意,因為我們規定了A的這一位必須選(因為狀態的最后一維是1),所以所有前驅狀態一定是f[i-1][j-1][…][…]。

然后討論另一種情況:這個字符不選。

這個比較簡單,到這個狀態有兩種方法:

1. 上一位沒有選,現在仍然不選

2. 上一位選了,結束這個子串

因此,我們有

f[i][j][k][0]=f[i-1][j][k][0]+f[i-1][j][k][1]。

合起來就是

f[i][j][k][1]=f[i-1][j-1][k-1][0]+f[i-1][j-1][k][1]+f[i-1][j-1][k-1][1](a[i]=b[j])

f[i][j][k][1]=0(a[i]!=b[j])

f[i][j][k][0]=f[i-1][j][k][0]+f[i-1][j][k][1]

狀態轉移方程有了,邊界也容易確定:f[0][0][0][0]=1。至於最終答案,顯然是f[n][m][k][0]+f[n][m][k][1]。

這里有O(nmk)個狀態,轉移是O(1)的,因此總復雜度O(nmk),完全夠用(畢竟常數不大)。

然后,注意一些可能越界的問題(j/k=0的時候不要j/k-1),再用滾動數組壓掉第一維,就可以AC了。

貼個代碼:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1010,maxm=210;
int n,m,c,i=1,f[2][maxm][maxm][2];
char a[maxn],b[maxm];
int main(){
#define MINE
#ifdef MINE
    freopen("2015substring.in","r",stdin);
    freopen("2015substring.out","w",stdout);
#endif
    scanf("%d%d%d %s %s",&n,&m,&c,a+1,b+1);
    f[0][0][0][0]=1;
    for(int d=1;d<=n;d++,i=!i)for(int j=0;j<=d&&j<=m;j++)for(int k=0;k<=j&&k<=d&k<=c;k++){
        f[i][j][k][0]=0;
        if(d-1>=j){
            (f[i][j][k][0]+=f[!i][j][k][0])%=1000000007;
            (f[i][j][k][0]+=f[!i][j][k][1])%=1000000007;
        }
        f[i][j][k][1]=0;
        if(j&&a[d]==b[j]){
            if(k){
                (f[i][j][k][1]+=f[!i][j-1][k-1][0])%=1000000007;
                (f[i][j][k][1]+=f[!i][j-1][k-1][1])%=1000000007;
            }
            (f[i][j][k][1]+=f[!i][j-1][k][1])%=1000000007;
        }
    }
    printf("%d\n",(f[!i][m][c][0]+f[!i][m][c][1])%1000000007);
#ifndef MINE
    printf("\n--------------------DONE--------------------\n");
    for(;;);
#endif
    return 0;
}
View Code

【后記】

去年聯賽的Day2 T2……難度還可以,主要是狀態表示和轉移方程比較麻煩,也不太好想,有些細節問題略惡心。

很久沒刷過DP了……自己DP本來就弱,不過好歹自己想出來了解法,也算是個安慰吧(我才不會說其實我已經從各種渠道知道了這題的復雜度是O(nmk)的)。

為了這題廢了一節課……努力吧……


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM