POJ3691 DNA repair AC自动机+动态规划

Problem Address:http://poj.org/problem?id=3691

 

【前言】

 

关于这道题,我看了好几天。

 

刚开始确实很不好理解,不知道如何动态规划法。

 

不过慢慢地就看懂了。

 

看了很多解题报告,不过写的都不是很清楚。无奈最后终于又看了代码,才渐渐了解过来。

 

我觉得,很多题目都不是简单地考察一个知识点,而是多个知识点的结合。而我们要学习的,也正是这种发现能力。

 

不多说,看思路。

 

【思路】

 

这道题我觉得主要是在构造AC自动机上,不是纯粹地构造。

 

当然,首先要正常地构造建立一棵Tire树(以所有病毒字符串为词典)。

 

接下来就是构造AC自动机了(先学习完AC自动机)。

 

使用BFS的形式构造。

 

注意点一:为每个结点构造失败指针的同时,检查其失败指针所指向的节点是否为危险节点,如果是的话也需要把当前节点标记为危险节点。

 

所谓的危险节点,就是指以当前节点为结尾的字符串是某个病毒串,或者包含了某个病毒串。如果在bfs的同时进行这项操作的时候就非常方便,在构造完当前失败指针的同时检查其所指向的节点是否为危险节点。

 

注意点二:如果某个节点的子节点不存在,则需要把它指向当前节点失败指针所指节点的子节点。

 

或者说这其实是属于AC自动机的一部分,当查找到某个结尾时返回到某个可查找的地方。同样地,在bfs的同时进行,如果子节点为空则把它指向当前节点失败指针所指节点的对应子节点。

 

这样的话,AC自动机的部分就完成了。接下来是DP部分。

 

dp[i][j],表示到达查找串第i个字符时,对应于AC自动机的j节点所需要的最小改变数。

 

状态转移为dp[i][j] = min( dp[i][j], dp[i-1][j]+code(s[i-1])!=k))

 

读到i个字符时,对应于j状态(DP的过程要两重循环i和j),要转移到son[j](j的子节点状态,在这里用k在[0,3]一重循环遍历所有可以转字符),如果第i个字符跟所要转移到的字符相同,则代价为0,因为不需要改变;否则代价为1,因为需要改变。

 

注意点三:如果当前状态不可达,则不需要对其进行后续运算。

 

注意点四:如果子节点为危险节点,则不可以进行转移,即不可以使用上面的状态方程。

 

最后,循环dp[len][j],即在读完最后一个字符后检查所有状态的最终值,取其最小。如果均不可达,则返回-1。

 

到此,这道题就完成了。

 

【代码】

 

#include #include using namespace std; struct node { int index; node *fail; node *next[4]; int count; }tire[1005]; int total; node *root; char keyword[25]; char str[1005]; node *q[1005]; int head, tail; int dp[1005][1005]; const int inf = 100000000; inline int min(int a, int b) { if (a<=b) return a; else return b; } inline int getcode(char ch) { switch(ch) { case 'A': return 0; case 'T': return 1; case 'C': return 2; case 'G': return 3; } return 0; } node* new_node() { node *p = &tire[total]; p->index = total; total++; p->count = 0; p->fail = NULL; memset(p->next, 0, sizeof(p->next)); return p; } void insert(node *root, char *s) { node *p = root; int i=0, index; while(s[i]!='/0') { index = getcode(s[i]); if (p->next[index]==NULL) p->next[index] = new_node(); p = p->next[index]; i++; } p->count = 1; } void build_ac_automation(node *root) { int i; node *temp, *p; node *flag; root->fail = NULL; head = 1; tail = 0; q[0] = root; while(head!=tail) { temp = q[tail]; tail++; flag = temp; p = NULL; for (i=0; i<4; i++) { if (temp->next[i]!=NULL) { if (temp==root) temp->next[i]->fail = root; else { p = temp->next[i]; temp->next[i]->fail = temp->fail->next[i]; if (temp->next[i]->fail->count!=0) temp->next[i]->count = 1; } q[head] = temp->next[i]; head++; } else { if (temp==root) temp->next[i] = root; else temp->next[i] = temp->fail->next[i]; } } } } int solve(char *s) { int len = strlen(s); int i,j,k; node *p; for (i=0; i<=len; i++) { for (j=0; jcount==0) { p = tire[j].next[k]; dp[i][p->index] = min(dp[i][p->index], dp[i-1][j]+(getcode(s[i-1])!=k)); } } } } } int ans = inf; for (i=0; i

 

【P.S】

 

AC自动机的思想很好。

 

动态规划的思想也很好。

 

两者结合更好。

 

关键是能够发现是两者的结合。

 

以及能够设计解答出来。

你可能感兴趣的:(POJ3691 DNA repair AC自动机+动态规划)