題目鏈接
題意簡述
設字符集爲ATCG,給出一個長爲\(n(n \le 15)\)的字符串\(A\),問有多少長度爲\(m(m \le 1000)\)的字符串\(B\)與\(A\)的最長公共子序列爲\(i\),對所有\(0 \le i \le n\)輸出答案。
題解
傳說中的計算機理論科科科科科學家cls的DP套DP。
因爲看別人寫的題解我都看不懂……所以我在這篇題解中,換一種方式講解,從暴力一點點優化得到DP套DP,應該更容易理解。
暴力怎麼寫呢?顯然是枚舉所有可能的字符串\(B\),然後對每一個都用經典的DP,求出與\(A\)的LCS。寫個僞代碼:
dfs(cur)
if(cur > m)
for(i: 1 -> m)
for(j: 1 -> n)
f[i][j] = max(f[i - 1][j], f[i][j - 1])
if(b[i] == a[j]) f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1)
ans[f[m][n]]++
return;
for(c in {A, T, C, G})
b[cur] = c
dfs(cur + 1)
考慮略微更改一下暴力的順序,從把字符串枚舉完再DP求LCS,變成一邊枚舉一邊DP,並把\(f[cur]\)傳入到遞歸函數中。
dfs(cur, f[])
if(cur > m)
ans[f[n]]++
return;
for(c in {A, T, C, G})
for(i : 1 -> n)
newf[i] = max(f[i], newf[i - 1])
if(c == a[i]) newf[i] = max(newf[i], f[i - 1] + 1)
dfs(cur + 1, newf)
往函數裏傳一個數組顯然非常菜,考慮狀壓這個\(f\)數組。顯然,一行f數組的每一位\(f[i]\)要麼比\(f[i - 1]\)多1,要麼和\(f[i - 1]\)相同。那麼用一個長爲\(n\)的二進制數狀壓這個\(f\)數組的差分即可。僞代碼(\(cnt1(s)\)表示二進制數\(s\)中1的個數,此時就等於\(f[n]\)):
dfs(cur, s)
if(cur > m)
ans[cnt1(s)]++
return;
for(c in {A, T, C, G})
for(i : 1 -> n)
f[i] = f[i - 1] + (s >> (i - 1) & 1)
for(i : 1 -> n)
newf[i] = max(f[i], newf[i - 1])
if(c == a[i]) newf[i] = max(newf[i], f[i - 1] + 1)
for(i: 1 -> n)
t |= (f[i] - f[i - 1]) << (i - 1)
dfs(cur + 1, t)
\(s\)顯然有很多重複的,每層DFS都這樣算一遍非常浪費,因爲這段代碼中\(s\)對應的\(t\)只和\(c\)有關,不如預處理出每個\(s\)在\(B[cur] == c\)時能轉移到哪個狀態\(t\)(預處理方法就和上面這段代碼中的那部分一樣)。設這個狀態\(t\)爲\(trans[s][c]\)。
dfs(cur, s)
if(cur > m)
ans[cnt1(s)]++
return;
for(c in {A, T, C, G})
dfs(cur + 1, trans[s][c])
這個DFS都變成這樣了,忍不住考慮能不能把它變成DP。用\(dp[i][s]\)表示字符串\(B\)長爲\(i\),對應的數組\(f\)狀壓後爲\(s\)的方案數。
dp[0][0] = 1
for(i : 1 -> m)
for(s: 1 -> (1 << n) - 1)
for(c in {A, T, C, G})
dp[i][trans[s][c]] += dp[i - 1][s]
for(s: 1 -> (1 << n) - 1)
ans[cnt1(s)] += dp[m][s]
至此你就從DFS暴力一步步優化出了這道題的DP套DP解法!
AC代碼
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
#define space putchar(' ')
#define enter putchar('\n')
using namespace std;
typedef long long ll;
template <class T>
void read(T &x){
char c;
bool op = 0;
while(c = getchar(), c < '0' || c > '9')
if(c == '-') op = 1;
x = c - '0';
while(c = getchar(), c >= '0' && c <= '9')
x = x * 10 + c - '0';
if(op) x = -x;
}
template <class T>
void write(T x){
if(x < 0) putchar('-'), x = -x;
if(x >= 10) write(x / 10);
putchar('0' + x % 10);
}
const int N = 15, M = 1005, P = 1000000007;
int T, n, m, id[128], a[N];
int bcnt[1<<N], trans[1<<N][4], f[2][1<<N];
char str[N];
void init_trans(){
static int pre[N], cur[N];
for(int s = 0; s < (1 << n); s++){
if(s) bcnt[s] = bcnt[s >> 1] + (s & 1);
pre[0] = s & 1;
for(int i = 1; i < n; i++)
pre[i] = pre[i - 1] + (s >> i & 1);
for(int c = 0; c < 4; c++){
int t = 0;
cur[0] = pre[0];
if(c == a[0]) cur[0] = 1;
t |= cur[0];
for(int i = 1; i < n; i++){
cur[i] = max(cur[i - 1], pre[i]);
if(c == a[i]) cur[i] = max(cur[i], pre[i - 1] + 1);
t |= (cur[i] - cur[i - 1]) << i;
}
trans[s][c] = t;
}
}
}
void inc(int &x, int y){
x += y;
if(x >= P) x -= P;
}
void calc_f(){
int pre = 1, cur = 0;
memset(f[1], 0, sizeof(f[1]));
f[1][0] = 1;
for(int i = 0; i < m; i++){
for(int s = 0; s < (1 << n); s++)
f[cur][s] = 0;
for(int s = 0; s < (1 << n); s++)
if(f[pre][s]){
for(int c = 0; c < 4; c++)
inc(f[cur][trans[s][c]], f[pre][s]);
}
swap(pre, cur);
}
static int ans[N + 1];
for(int i = 0; i <= n; i++) ans[i] = 0;
for(int s = 0; s < (1 << n); s++)
inc(ans[bcnt[s]], f[pre][s]);
for(int i = 0; i <= n; i++)
write(ans[i]), enter;
}
int main(){
id['A'] = 0, id['T'] = 1, id['C'] = 2, id['G'] = 3;
read(T);
while(T--){
scanf("%s%d", str, &m);
n = strlen(str);
for(int i = 0; i < n; i++)
a[i] = id[int(str[i])];
init_trans();
calc_f();
}
return 0;
}