【POJ3691】DNA repair AC自动机+DP

题意:给出n个疾病基因片段,和一个完整基因,求最少修改多少个点能使基因不含疾病片段,

    如样例数据:

2
AAA
AAG
AAAG    
2
A
TG
TGAATG
4
A
G
C
T
AGT
0
答案就是

Case 1: 1
Case 2: 4
Case 3: -1

意为AAAG中改一个字符就可以不含上面两个片段(任意A改成C或者T)。

如果改不过来(如Case 3)则输出‘-1’。


题解:首先建立AC自动机,然后for循环进行动规(我的代码沙茶了,竟然将他们入队!入队?!!)

动规的状态f[i][j]表示前i个字符跑一遍字典树且得到AC自动机中节点j时无疾病的最小修改字符个数。

如f[1][0]就表示在root上,且目标串第0个字符for循环完毕时的状态。

转移很好想,代码也比较清晰,是以下个字符为ATGC来转移。

其实说起来不是很容易,还是看代码吧。

#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 1010
#define T 4
#define inf 0x3f3f3f3f
using namespace std;
bool end[N];/*是否为一个单词的结尾*/
int fail[N],next[N][T],cnt;/*失败指针、向其他字典树节点的指针、最后一个节点的序号*/
int n,g;
char s[N],tt[25];
int f[N][N];  /*         状态,不赘述了           */
int crs[123];/*一个小小的映射(算是不必要优化吧)*/
queue<int> q;
void init()
{
	printf("Case %d: ",++g);
	memset(f,0x3f,sizeof(f));
	memset(end,0,sizeof(end));
	memset(fail,0,sizeof(fail));
	memset(next,0,sizeof(next));
	cnt=0;f[0][0]=0;/**********注意!**********/
}
void insert()
{
	int temp=0,i,alp;
	scanf("%s",tt);
	for(i=0;tt[i];i++)
	{
		alp=crs[tt[i]-'A'+'A'];
		if(!next[temp][alp])next[temp][alp]=++cnt;
		temp=next[temp][alp];
	}
	end[temp]=1;
}
void acauto()
{/*找fail我就不赘述了*/
	while(!q.empty())q.pop();
	int temp,i,u,v;
	q.push(0);
	while(!q.empty())
	{
		u=q.front();q.pop();
		for(i=0;i<T;i++)if(next[u][i])
		{
			v=next[u][i];
			if(!u)fail[v]=0;
			else
			{
				temp=fail[u];
				while(temp&&!next[temp][i])temp=fail[temp];
				fail[v]=next[temp][i];
				end[v]|=end[fail[v]];
				 /*end的意义在这里已经不是单纯的“单词结尾了” */
				/*			而是表示有病与否!    */
			}
			q.push(v);
		}
	}
}
void dp(int x)
{
	int alp,temp,i,u,v;
	while(!q.empty())q.pop();
	/*这里其实写的很逗,完全没有必要用队列,直接开一个for循环就好了!*/
	for(i=0;i<=cnt;i++)q.push(i);
	alp=crs[s[x]-'A'+'A'];
	while(!q.empty())
	{/*直接for循环,写队列反而恶心+很慢*/
		u=q.front();q.pop();
		if(end[u]||f[x][u]==inf)continue;
		for(i=0;i<T;i++)/*以ATGC为下个字符来进行转移*/
		{
			temp=u;
			while(temp&&!next[temp][i])temp=fail[temp];
			v=next[temp][i];/*寻找一个节点,使其表示单词长度最大且为当前字串后缀*/
			if(end[v])continue;/*疾病基因!直接抛弃该节点!*/
			if(i==alp)f[x+1][v]=min(f[x+1][v],f[x][u]);	 /*两种转移*/
			else f[x+1][v]=min(f[x+1][v],f[x][u]+1);	/*(根据需不需要修改来决定后面加上的1)*/
		}
	}
}
int main()
{
	int i,mini,len;
	crs['A']=0;
	crs['T']=1;
	crs['G']=2;
	crs['C']=3;
	/*形成对照,以便于在dp函数和建立字典树时不用多if判断*/
	while(scanf("%d",&n),n)
	{
		init();/*初始化(清数组/赋初值)*/
		for(i=1;i<=n;i++)insert();/*建立字典树*/
						 acauto();/*维护失败指针(fail)*/
		scanf("%s",s);
		len=strlen(s);
		for(i=0,mini=inf;i<len;i++)dp(i);/*动规转移状态*/
		for(i=0;i<=cnt;i++)mini=min(mini,f[len][i]);/*找出答案*/
		if(mini>len)printf("-1\n");
		else printf("%d\n",mini);
	}
	return 0;
}


你可能感兴趣的:(题解,AC自动机,动规,POJ3691)