HDU 2457 DNA repair AC自动机 + dp
http://acm.hdu.edu.cn/showproblem.php?pid=2457
首先把病毒串保存一下,然后对于每一个trie上的节点,跑一发AC自动机,建立一个trie图。
建立的时候,对应做一些修改。
比如,现在建立成了这个样子。
如果he是一个病毒串,那么应该相对应的,把she那个he的位置,标志上,它也是病毒串,也就是不能转移到这一个状态。
这个可以在buildfail的时候对应修改。
dp,
设dp[i][j],表示处理到字符串的第i个,走到了AC自动机的第j个节点,变成了一个合法串的最小代价。
那么ans = min(ans, dp[lenstr][0....all]);就是走到哪一个节点都可以。因为自动机能判断出能不能走到这个节点,因为可能是bad串。
dp[0][0(root)] = 0, else = inf
然后就是变化的问题了,如果匹配失败,那么应该去到father->fail->next[id]
这个可以结合第二个样例看看。
串是TGAATG
当第处理到第5个字符T的时候,现在走到trie的A那个节点,但是A没有T这个儿子,那么就需要转移到root->T了,因为这样,能够为后面的不能进入TG做准备,TG是一个bad串。
具体看看代码吧,我也是看别人代码懂得。。
#include <cstdio> #include <cstdlib> #include <cstring> #include <cmath> #include <algorithm> #include <assert.h> #define IOS ios::sync_with_stdio(false) using namespace std; #define inf (0x3f3f3f3f) typedef long long int LL; #include <iostream> #include <sstream> #include <vector> #include <set> #include <map> #include <queue> #include <string> #include <bitset> int n; const int maxn = 50 + 20; const int N = 4; struct node { int flag; struct node *Fail; //失败指针,匹配失败,跳去最大前后缀 struct node *pNext[N]; int id; } tree[maxn * 20]; int t; //字典树的节点 struct node *create() { //其实也只是清空数据而已 struct node *p = &tree[t++]; p->flag = 0; p->Fail = NULL; p->id = t - 1; for (int i = 0; i < N; i++) { p->pNext[i] = NULL; } return p; } int getid(char ch) { if (ch == 'A') return 0; else if (ch == 'G') return 1; else if (ch == 'C') return 2; else return 3; } void toinsert(struct node **T, char str[]) { struct node *p = *T; if (p == NULL) { p = *T = create(); } for (int i = 1; str[i]; i++) { int id = getid(str[i]); if (p->pNext[id] == NULL) { p->pNext[id] = create(); } p = p->pNext[id]; } p->flag++; //相同的单词算两次 return ; } void BuiltFail(struct node **T) { //根节点没有失败指针,所以都是需要特判的 //思路就是去到爸爸的失败指针那里,找东西匹配,这样是最优的 struct node *p = *T; //用个p去代替修改 struct node *root = *T; if (p == NULL) return ; //树上bfs,要更改的是p->pNext[i]->Fail struct node *que[t + 20]; //这里的t是节点总数,字典树那里统计的 int head = 0, tail = 0; que[tail++] = root; while (head < tail) { p = que[head]; //p取出第一个元素 ★ for (int i = 0; i < N; i++) { //看看存不存在这个节点 if (p->pNext[i] != NULL) { //存在的才需要管 if (p == root) { //如果爸爸是根节点的话 p->pNext[i]->Fail = root; //指向根节点 } else { struct node *FailNode = p->Fail; //首先找到爸爸的失败指针 while (FailNode != NULL) { if (FailNode->pNext[i] != NULL) { //存在 p->pNext[i]->Fail = FailNode->pNext[i]; break; } FailNode = FailNode->Fail; //回溯 } if (FailNode == NULL) { //如果还是空,那么就指向根算了 p->pNext[i]->Fail = root; } } if (p->pNext[i]->Fail->flag) { p->pNext[i]->flag = 1; //它是病毒,我也是病毒。 } que[tail++] = p->pNext[i]; } else if (p == root) { //变化问题 p->pNext[i] = root; } else { p->pNext[i] = p->Fail->pNext[i]; } } head++; } return ; } char str[1111]; int lenstr, dp[1111][1111]; void work() { t = 0; struct node *T = NULL; for (int i = 1; i <= n; ++i) { scanf("%s", str + 1); toinsert(&T, str); } scanf("%s", str + 1); lenstr = strlen(str + 1); for (int i = 1; i <= lenstr; ++i) str[i] = getid(str[i]); BuiltFail(&T); --t; for (int i = 0; i <= lenstr; ++i) { for (int j = 0; j <= t; ++j) { dp[i][j] = inf; } } dp[0][0] = 0; for (int i = 1; i <= lenstr; ++i) { for (int j = 0; j <= t; ++j) { if (dp[i - 1][j] == inf) continue; for (int k = 0; k < 4; ++k) { if (tree[j].pNext[k]->flag) continue; //bad串 int s = tree[j].pNext[k]->id; dp[i][s] = min(dp[i][s], dp[i - 1][j] + (str[i] != k)); } } } int ans = inf; for (int i = 0; i <= t; ++i) { ans = min(ans, dp[lenstr][i]); } if (ans == inf) ans = -1; static int gg = 0; printf("Case %d: %d\n", ++gg, ans); } int main() { #ifdef local freopen("data.txt", "r", stdin); // freopen("data.txt", "w", stdout); #endif while (scanf("%d", &n) != EOF && n) work(); return 0; }
既然选择了远方,就要风雨兼程~
posted on 2017-02-28 23:05 stupid_one 阅读(268) 评论(0) 编辑 收藏 举报