HDU - 2457:AC自动机 + DP

    xiaoxiao2022-07-04  214

    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; }

     

     

    最新回复(0)