hdu 2457
题意:
依旧是DNA序列的问题。给出一些致病的基因片段(仅由’A’,’G’,’C’,’T’组成),和一个DNA序列T,可以在序列T上做修改操作,修改某一个碱基为另一个碱基记作一次修改操作。问你最少修改多少次使得序列T不包含任何已给出的致病基因片段。
思路:
多串匹配,依旧是AC自动机。关于trie图与DNA序列的关系可以参考poj 1625
接下来是dp的部分
题目要求最少修改次数。DNA序列的形成可以理解为在trie图上走n步的情况。设计状态dp[i][j]
表示长度为i的串在节点j时所需的最少修改次数。由Trie图的性质可知,第j->next[k]的状态节点必定由j节点转移而来。也就是说dp[i][j]
与所有dp[i-1][k](k到j有一条有向边)
有关。
他们之间的关系即dp转移方程是dp[i][j->next[k]] = min(dp[i][j->next[k]],dp[i-1][j]+s[i-1]!='k')
这个方程该如何理解呢?
题目中的修改操作以及不修改操作可以统一为trie图上沿着有向边的状态转移,只不过转出的节点不同且修改操作需要dp值加一,而对于每个状态dp[i][j->next[k]]的值是所有dp[i-1][j]+t(若s[i-1]即长度为i的串的最后一个字符也就是当前匹配的字符==k在字符集中对应的字符(对于此题,k=0,1,2,3分别对应于’A’,’G’,’C’,’T’),t=0;否则t=1)的最小值。
换一种方法来说,dp[i][j]
是所有(dp[i-1][k](k到j在trie图上有一条有向边)+k到j转移所需的修改次数(取值为0或1))
的最小值。
那么最后的ans=min(dp[slen][m])(0<=m<sz,slen为原DNA序列T的长度)
。
经过分析,dp数组初始化为inf(即设定的某个最大值。若dp初值为0,则违背了dp数组的含义,表示任意长度字串到任意状态需要的修改次数均为0)。dp[0][0]=0
dp的起点也就是空串。
dp状态第一维是字串长度,第二维是trie图上的节点,除根节点外,其他所有节点都表示当前字符串以该节点所代表的字符结尾。而根节点dp[0][0]
表示空串,dp[i][0](i>0)
表示字串以字符集中的字符结尾,具体的字符取决于转移边所代表的字符,但一定不是空串。
也就是说,trie图根节点入边的字符可能是不确定的,而其他节点的入边所表示的字符是一定相同的。
注意事项:
危险节点,也就是原trie图中的致病基因结尾节点要删除。中间节点的last[]若不为根节点,也要删除。
下面贴代码:
#include <cstdio>
#include <cstring>
#include <queue>
#define maxnode 1005
#define sigma_size 4
#define length 1005
#define inf 2000
using namespace std;
int ch[maxnode][sigma_size];
int val[maxnode];
int f[maxnode];
int last[maxnode];
int sz;
int dp[length][maxnode];
char ban[25];
char s[length];
int n;
int slen;
char charset[] = {'A', 'G', 'C', 'T'};
void initial()
{
memset(ch[0], 0, sizeof(ch[0]));
sz = 1;
}
int getch(char a)
{
switch(a)
{
case 'A':
return 0;
case 'G':
return 1;
case 'C':
return 2;
case 'T':
return 3;
}
}
void insert(char *T)
{
int l = strlen(T);
int u = 0;
for(int i = 0; i < l; i++)
{
int c = getch(T[i]);
if(!ch[u][c])
{
memset(ch[sz], 0, sizeof(ch[sz]));
val[sz] = 0;
ch[u][c] = sz++;
}
u = ch[u][c];
}
val[u] = 1;
}
void getfail()
{
queue <int> q;
f[0] = 0;
for(int c = 0; c < sigma_size; c++)
{
int u = ch[0][c];
if(u)
{
f[u] = 0;
q.push(u);
last[u] = 0;
}
}
while(!q.empty())
{
int r = q.front();
q.pop();
for(int c = 0; c < sigma_size; c++)
{
int u = ch[r][c];
if(!u)
{
ch[r][c] = ch[f[r]][c];
continue;
}
q.push(u);
int v = f[r];
while(v && !ch[v][c]) v = f[v];
f[u] = ch[v][c];
last[u] = val[f[u]] ? f[u] : last[f[u]];
}
}
}
void dps()
{
for(int i = 0; i <= slen;i++)
for(int j = 0; j < sz; j++)
dp[i][j] = inf;
dp[0][0] = 0;
for(int i = 1; i <= slen; i++)
{
for(int j = 0; j < sz; j++)
{
if(val[j] || last[j])
{
continue;
}
for(int k = 0; k < 4; k++)
{
if(val[ch[j][k]] || last[ch[j][k]])
continue;
int now = dp[i - 1][j];
if(s[i - 1] != charset[k])
now += 1;
if(now < dp[i][ch[j][k]])
dp[i][ch[j][k]] = now;
}
}
}
}
int main()
{
int tt = 0;
while(~scanf("%d", &n) && n)
{
++tt;
initial();
for(int i = 0; i < n; i++)
{
scanf("%s", ban);
insert(ban);
}
getfail();
scanf("%s", s);
slen = strlen(s);
dps();
int ans = inf;
for(int i = 0; i < sz; i++)
if(dp[slen][i] < ans)
ans = dp[slen][i];
if(ans == inf)
ans = -1;
printf("Case %d: %d\n",tt,ans);
}
return 0;
}