【NOIP2015提高组】子串 区间DP+滚动数组优化
題意:
有兩個僅包含小寫英文字母的字符串?A?和?B。
現在要從字符串?A?中取出?k?個互不重疊的非空子串,然后把這?k?個子串按照其在字符串?A?中出現的順序依次連接起來得到一個新的字符串。
請問有多少種方案可以使得這個新串與字符串?B?相等?
注意:子串取出的位置不同也認為是不同的方案。
?
數據范圍:
對于第 1 組數據:1≤n≤500,1≤m≤50,k=1;
對于第 2 組至第 3 組數據:1≤n≤500,1≤m≤50,k=2
對于第 4 組至第 5 組數據:1≤n≤500,1≤m≤50,k=m
對于第 1 組至第 7 組數據:1≤n≤500,1≤m≤50,1≤k≤m
對于第 1 組至第 9 組數據:1≤n≤1000,1≤m≤100,1≤k≤m
對于所有 10 組數據:1≤n≤1000,1≤m≤200,1≤k≤m
------------------------------------------------------我是分割線----------------------------------------------------
題解:一道狀態不太明顯的題,我們首先要尋找這道題的切入點。
觀察這道題的特點,我們發現,相對于B,A中的每個字符顯然有選與不選兩種決策。
而對于這兩個字符串,可以用兩個指針表示,一個指向A的狀態,一個指向B的狀態。
于是這道題的狀態就很被發現了。
設置狀態:
設 F(i,j,k,1) 表示A的前i個字符中,匹配到B中當前第j個字符,一共使用了k段,選擇當前第i個字符的方案數。
F(i,j,k,0)??表示A的前i個字符中,匹配到B中當前第j個字符,一共使用了k段,不選擇當前第i個字符的方案數。
然后就是狀態轉移:
由狀態,我們可以推出如下狀態轉移方程:
(1) 當Ai == Bj 時,F(i,j,k,0) = F(i-1,j,k,0)+ F(i-1,j,k,1).
? 即當前A中第i個數不作為答案的方案數 == 前i-1的方案數總和。
? F(i,j,k,1) = F(i-1,j-1,k-1,0) + F(i-1,j-1,k-1,1) + F(i-1,j-1,k,1);
? ?即當前A中第i個數作為答案的方案數 == A中前i-1個數匹配到B中第j-1個的方案數總和(i不接到前一個答案中) + A中前i-1個數方案(i接到前一個答案中)。??
(2)當Ai? != Bj 時, F(i,j,k,0) = F(i-1,j-1,k,0) + F(i-1,j-1,k,1).
? ? ? 同(1)中的轉移。
? F(i,j,k,1) = 0; ?
由于當前數不能作為答案,所以方案為0.
再確定邊界條件,我們可以知道,F(i,0,0,0) = 1;即匹配B中0個字符的方案為1.
于是我們可以寫出代碼:
#include<bits/stdc++.h>#define ll long long #define mp make_pair #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define per(i, a, b) for(int i = (a); i >= (b); i--)using namespace std;typedef pair<int, int> pii; typedef double db; const int mod = 1e9+7; const int N = 1e6 + 50; int n, m, p; int f[1010][101][101][2]; char a[N], b[N]; inline int read() {int x = 0, f = 1;char ch = getchar();while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar();}while(ch >='0' && ch <='9') { x = (x<<3)+(x<<1)+(ch^48); ch = getchar();}return x*f; } void work(){rep(i, 0, n) f[i][0][0][0] = 1;rep(i, 1, n) rep(j, 1, m) rep(k, 1, p){if(a[i] == b[j]) {f[i][j][k][0] = (f[i-1][j][k][0] + f[i-1][j][k][1])%mod;f[i][j][k][1] = (f[i-1][j-1][k-1][0] + (f[i-1][j-1][k-1][1] + f[i-1][j-1][k][1]) % mod) % mod;}else {f[i][j][k][1] = 0;f[i][j][k][0] = (f[i-1][j][k][0] + f[i-1][j][k][1])%mod;}}printf("%d\n", (f[n][m][p][1] + f[n][m][p][0])%mod); } void init(){n = read(); m = read(); p = read();scanf("%s%s", a+1, b+1); } int main(){init(); work();return 0; } View Code但是我們發現,這份代碼空間復雜度效率低下(2*n*m*k),無法通過此題,我們還需要優化。
于是乎,DP常用的空間優化:滾動數組優化就出現了。
觀察DP轉移方程,我們可以發現,每一個決策i只與前一個決策i-1有關,其他的空間都是多余的。
所以我們就可以用01方法表示。
AC代碼如下:
#include<bits/stdc++.h>#define ll long long #define mp make_pair #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define per(i, a, b) for(int i = (a); i >= (b); i--)using namespace std;typedef pair<int, int> pii; typedef double db; const int mod = 1e9 + 7; const int N = 1e6 + 50; int n, m, p; int f[2][220][220][2]; char a[N], b[N]; inline int read() {int x = 0, f = 1;char ch = getchar();while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar();}while(ch >='0' && ch <='9') { x = (x<<3)+(x<<1)+(ch^48); ch = getchar();}return x*f; } void work(){int val = 1;f[0][0][0][0] = f[1][0][0][0] = 1; rep(i, 1, n) {rep(j, 1, m) rep(k, 1, p){if(a[i] == b[j]) {f[val][j][k][0] = (f[val^1][j][k][0] + f[val^1][j][k][1])%mod;f[val][j][k][1] = (f[val^1][j-1][k-1][0] + (f[val^1][j-1][k-1][1] + f[val^1][j-1][k][1]) % mod) % mod;}else {f[val][j][k][1] = 0;f[val][j][k][0] = (f[val^1][j][k][0] + f[val^1][j][k][1])%mod;}}val ^= 1;}printf("%d\n", (f[n&1][m][p][1] + f[n&1][m][p][0])%mod); } void init(){n = read(); m = read(); p = read();scanf("%s%s", a+1, b+1); } int main(){init(); work();return 0; } View Code總結:這道題作為線性DP的練習題(NOIP的題),有一定的思維難度,對DP思維提升有很大的幫助。
轉載于:https://www.cnblogs.com/smilke/p/11587313.html
總結
以上是生活随笔為你收集整理的【NOIP2015提高组】子串 区间DP+滚动数组优化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 以命令方式从ftp服务器上下载和上传文件
- 下一篇: 子网掩码与子网个数、主机地址个数的关系