基因的庇護
10.25
思路:
一道比較典型的AC自動機上DP,只不過需要分析一下性質。
由於每個位置都需要被庇護序列覆蓋到,用dp[i][j][k]表示鏈長爲i,在AC自動機上的j號點,當前鏈上倒數第k個位置是最左的沒有被覆蓋到的位置時的方案數。預處理出AC自動機上每個結點表示的串的最長的有庇護效應的後綴長度,轉移時就看這個長度是否大於等於k+1,若大於等於則說明倒數第k個位置能被覆蓋到了,即轉移到dp[i+1][j'][0];否則轉移到dp[i+1][j'][k+1]。
把dp[n][j][0]求和即爲答案。
做不完的題,想不到的dp。。。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 105
#define MOD 1000000009
using namespace std;
int n, m, tot;
int head, tail;
char s[20];
int to[N][4], fail[N], cover[N];
int dp[1005][N][12], q[N];
inline void add(int &a, const int &b){
a += b;
if(a >= MOD) a -= MOD;
}
int re(char x){
if(x == 'A') return 0;
if(x == 'T') return 1;
if(x == 'C') return 2;
if(x == 'G') return 3;
}
void insert(){
int p = 0;
int len = strlen(s+1);
for(int i=1; i<=len; ++i){
if( !to[p][re(s[i])] ) to[p][re(s[i])] = ++tot;
p = to[p][re(s[i])];
}
cover[p] = len;
}
void get_fail(){
for(int i=0; i<4; ++i) if( to[0][i] ) q[tail++] = to[0][i];
while(head < tail){
int a = q[head++];
for(int i=0; i<4; ++i){
if( to[a][i] ){
fail[to[a][i]] = to[fail[a]][i];
q[tail++] = to[a][i];
cover[to[a][i]] = max(cover[to[a][i]], cover[fail[to[a][i]]]);
}
else to[a][i] = to[fail[a]][i];
}
}
}
void solve(){
dp[0][0][0] = 1;
for(int i=0; i<n; ++i){
for(int j=0; j<=tot; ++j){
for(int k=0; k<10; ++k){
if( !dp[i][j][k] ) continue;
for(int p=0; p<4; ++p){
int v = to[j][p];
if(cover[v] >= k+1) add(dp[i+1][v][0], dp[i][j][k]);
else add(dp[i+1][v][k+1], dp[i][j][k]);
}
}
}
}
int ans = 0;
for(int i=0; i<=tot; ++i) add(ans, dp[n][i][0]);
printf("%d\n", ans);
}
int main(){
freopen("protect.in", "r", stdin);
freopen("protect.out", "w", stdout);
scanf("%d%d", &n, &m) ;
for(int i=1; i<=m; ++i){
scanf("%s", s+1) ;
insert();
}
get_fail();
solve();
return 0;
}