【題目描述】
有兩個僅包含小寫英文字母的字符串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; }
【后記】
去年聯賽的Day2 T2……難度還可以,主要是狀態表示和轉移方程比較麻煩,也不太好想,有些細節問題略惡心。
很久沒刷過DP了……自己DP本來就弱,不過好歹自己想出來了解法,也算是個安慰吧(我才不會說其實我已經從各種渠道知道了這題的復雜度是O(nmk)的)。
為了這題廢了一節課……努力吧……