HDU - 2457:AC自动机 + DP

HDU - 2457

题意:最少改变多少个字符,使给定的字符串不包含以上的任意一个字串。

题解

  • dp[i][j]表示在第字符串的第i个位置,在第j个结点,不包含任意一个字串的最少改变次数。
  • 初始化为inf,dp[0][0] = 0;
  • 我们状态转移是有顺序的从root结点和0号位置开始的。fail树相当于从root开始一层一层的往外扩展,类似与bfs。
  • 如果我们在字符串的第i个位置,从j号结点向外扩展,首先要判断第i-1个位置是否到达j号结点,即判断dp[i-1][j]是否为inf,如果为inf,就不能从这个dp[i-1][j]开始转移。然后枚举j号结点可以向外扩展的结点,如果是危险结点,那么不能扩展。设向外扩展的结点为k,对应的字符为ss,那么dp[i][k] = min(dp[i][k],dp[i-1][j] + ss != s[i])。
  • 最后求min(dp[len][01…])

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
using namespace std;
int const inf = 0x3f3f3f3f;
int const M = 500000+ 10;
int const N = 1000 + 10;
int tot;
struct Node{
	Node *next[4];
	Node *fail;
	int cnt;   //判断是否是结尾,即危险结点
	int index;   //结点的标号
	Node(){
		for(int i=0;i<4;i++)	next[i] = NULL;
		fail = NULL;
		cnt = 0;
		index = tot++;
	}
}*q[M],*root,*trie[N];
int getnum(char s){
	if(s == 'A')	return 0;
	else if(s == 'C')	return 1;
	else if(s == 'T')	return 2;
	else 	return 3;
}
void Insert(char *s){
	int n = strlen(s);
	Node *now = root;
	for(int i=0;i<n;i++){
		int to = getnum(s[i]);
		if(now->next[to] == NULL){
			now->next[to] = new Node();
			trie[tot-1] = now->next[to];
		}
		now = now->next[to];
	}
	now->cnt = 1; 
}
void Get_Fail(){
	int head = 0,tail = 0;
	q[head++] = root;
	root->fail = NULL;
	while(head != tail){
		Node *tmp = q[tail++];
		Node *p;
		for(int i=0;i<4;i++){
			if(tmp->next[i] == NULL){   //空指针
				if(tmp == root)	tmp->next[i] = root;
				else	tmp->next[i] = tmp->fail->next[i];
				continue;
			}
			if(tmp == root)	tmp->next[i]->fail = root;
			else{
				p = tmp->fail;
				while(p){
					if(p->next[i]){
						tmp->next[i]->fail = p->next[i];
						if(tmp->next[i]->fail->cnt)	tmp->next[i]->cnt = 1;   //包含关系
						break;
					}
					p = p->fail;
				}
				if(p == NULL)	tmp->next[i]->fail = root;
			}
			q[head++] = tmp->next[i];
		}
	}
}
char s[25],str[N];
int dp[N][N];
int main(){
	int caser = 0;
	int n;
	while(~scanf("%d",&n) && n){
		tot = 0;
		root = new Node();
		trie[tot-1] = root;
		for(int i=1;i<=n;i++){
			scanf(" %s",s);
			Insert(s);
		}
		scanf(" %s",str+1);
		Get_Fail();
		memset(dp,inf,sizeof(dp));
		dp[0][0] = 0;
		int len = strlen(str+1);
		for(int i=1;i<=len;i++){  
			for(int j=0;j<tot;j++){   //从结点j出发,所以第i-1分字符必须要能够到达第j个结点
				if(dp[i-1][j] == inf)	continue;
				for(int k=0;k<4;k++){  
					if(trie[j]->next[k]->cnt)	continue;  //如果是危险结点
					int index = trie[j]->next[k]->index;  //第j个结点所能达到的最远的结点。
					dp[i][index] = min(dp[i][index],dp[i-1][j] + (k != getnum(str[i])));   //不相同则改变+1
				}
			}
		}
		int ans = inf;
		for(int i=0;i<tot;i++)
			ans = min(ans,dp[len][i]);
		printf("Case %d: %d\n",++caser,ans == inf ? -1 : ans);
	}
	return 0;
}


 

 

posted @ 2019-05-22 16:47  月光下の魔术师  阅读(12)  评论(0)    收藏  举报