AC自动机练习2:修改串
这道题的话用到了dp,一个比较简单的dp方程
1466: 【AC自动机】修改串
时间限制: 1 Sec 内存限制: 128 MB
提交: 18 解决: 14
[提交] [状态] [讨论版] [命题人:admin]题目描述
【题意】
给出n个模式串,然后给出一个修改串,求尽量少修改修改串,使得修改串不含有任何一个模式串,不能的话输出-1
每个串只有'A','C','G','T'四个字母
【输入格式】
有多组数据,输入以一个0结束
每组数据:
输入一个n(n<=50)
接下来n行输入n个模式串(每个模式串长度不超过20)
最后一行输入修改串(长度不超过1000)
【输出格式】
输出Case T: ans
T当前输出的是第T组数据,ans表示最少修改次数,不能修改则ans=-1
【样例输入】
2
AAA
AAG
AAAG
2
A
TG
TGAATG
4
A
G
C
T
AGT
0
【样例输出】
Case 1: 1
Case 2: 4
Case 3: -1
我第一眼看到这道题的时候,一度怀疑是模板题,然后定睛一看,没这么简单,应该我们在修改的时候要尽可能的找位置去修改更多的字符,所以这就意味这我们可能要用到最方便的继承状态的dp(当然作为一个dp盲人,我一开始是没有想到的,只有暴力才是王道),下面看一下讲解
说难的话就不能说特别难,只是有一些细节要弄清楚,
- 多组数据,一定要每一次询问前初始化
- 因为只有四个字符,所以我们可以将四个字符转化为数字,这样我们在处理判断的时候就会方便一些
- fail值初始化为-1,因为我们的f数组(也就是dp数组)0是有意义的
- 构造失败指针的时候用到了我很久以前讲的一个继承的知识点
然后一切问题都游刃而解,剩下的就看代码的实现吧
(注释版,我已经把细节讲清楚了,所以的话可以尝试自己挑战一下,然后再poj提交)
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<cstdlib> 5 #include<cmath> 6 #include<iostream> 7 using namespace std; 8 struct node 9 { 10 int s,fail,cnt[5]; 11 /*只有四个数字,所以cnt不用定义这么大 12 fail表示失败指针,s记录模式串的结尾*/ 13 }tr[2010]; 14 int tot,list[2010]; 15 char a[2010]; 16 void clean(int x)/*多组数据清空树*/ 17 { 18 tr[x].fail=-1; tr[x].s=0;/*为什么fail的初始化值要为-1呢,因为我们在构造失败指针的时候, 19 是把孩子节点直接继承失败指针,如果这个时候用0来区分的话,可能会炸掉*/ 20 memset(tr[x].cnt,-1,sizeof(tr[x].cnt)); 21 } 22 int id(char c)/*为了方便,我们把要处理的数字都直接转化成数字*/ 23 { 24 if(c=='A') return 1; 25 if(c=='C') return 2; 26 if(c=='G') return 3; 27 return 4; 28 } 29 void build_tree()/*建树板子*/ 30 { 31 int x=0; int len=strlen(a+1); 32 for(int i=1;i<=len;i++) 33 { 34 int y=id(a[i]); 35 if(tr[x].cnt[y]==-1) 36 { 37 tr[x].cnt[y]=++tot; 38 clean(tot); 39 } 40 x=tr[x].cnt[y]; 41 } 42 tr[x].s++; 43 } 44 void bfs()/*构造失败指针*/ 45 { 46 list[0]=1; int head=1,tail=1; 47 while(head<=tail) 48 { 49 int x=list[head]; 50 for(int i=1;i<=4;i++) 51 { 52 int son=tr[x].cnt[i]; 53 if(son==-1)/*没有孩子*/ 54 { 55 if(x==0) tr[x].cnt[i]=0;/*这里要等于0,因为如果不等于0的话,在下面dp会炸掉*/ 56 else tr[x].cnt[i]=tr[tr[x].fail].cnt[i];/*我在板子里面讲过是可以继承我fail指针的,这个是成立的*/ 57 continue; 58 } 59 if(x==0) tr[son].fail=0;/*根节点的fail值为0*/ 60 else 61 { 62 int j=tr[x].fail; 63 while(j!=-1)/*这个点存在*/ 64 { 65 if(tr[j].cnt[i]!=-1)/*有孩子节点*/ 66 { 67 tr[son].fail=tr[j].cnt[i];/*指向孩子节点,和上面的那个是一样的,可以继承*/ 68 int tt=tr[j].cnt[i]; 69 if(tr[tt].s!=0) tr[son].s=1;/*如果他的孩子节点是结尾的话,son也是作为结尾 70 因为继承所以一切都讲通了*/ 71 break; 72 } 73 j=tr[j].fail;/*继续继承*/ 74 } 75 if(j==-1) tr[son].fail=0;/*如果这个点不存在,那么x儿子的失败指针就指向根节点*/ 76 } 77 list[++tail]=son; 78 } 79 head++; 80 } 81 } 82 int f[2100][2100],p,n,ans; 83 /*f数组是用来运行dp的,p是输入的模式串的个数,n是修改串的长度,ans记录答案 84 f[i][j]表示当前在第i位(修改串),匹配到AC自动机上(字典树)的第j个结点, 85 转移时,考虑添加一个字符,在AC自动机上获取添加这个结点会转移到的下一个结点(字符串匹配),并判断这样转移是否形成了一个模式串。 86 读到i个字符时,对应于j状态(DP的过程要两重循环i和j),要转移到son[j](j的子节点状态,在这里用k在[1,4]一重循环遍历所有可以转字符), 87 如果第i个字符跟所要转移到的字符相同,则代价为0,因为不需要改变;否则代价为1,因为需要改变*/ 88 void dp() 89 { 90 for(int i=0;i<=n;i++) for(int j=0;j<=tot;j++) f[i][j]=999999999; f[0][0]=0;/*初始化*/ 91 for(int i=0;i<n;i++) 92 { 93 for(int j=0;j<=tot;j++) 94 { 95 if(f[i][j]==999999999) continue; 96 for(int k=1;k<=4;k++)/*四种状态*/ 97 { 98 int son=tr[j].cnt[k];/*要转移的状态*/ 99 if(tr[son].s) continue;/*已经是结尾就没有必要继续搜索了*/ 100 f[i+1][son]=min(f[i+1][son],f[i][j]+(id(a[i+1])!=k)); 101 /*下一位如果等于要转移的状态,代价为0,否则就为1*/ 102 } 103 } 104 } 105 ans=999999999; 106 for(int i=0;i<=tot;i++) ans=min(ans,f[n][i]); 107 if(ans==999999999) ans=-1;/*一遍一遍更新答案*/ 108 } 109 int main() 110 { 111 int t=0; while(scanf("%d",&p)!=EOF && p)/*多组数据*/ 112 { 113 t++; tot=0; clean(0);/*t组数据,多组数据初始化*/ 114 for(int i=1;i<=p;i++) 115 { 116 scanf("%s",a+1); 117 build_tree();/*输入建树*/ 118 } 119 scanf("%s",a+1); n=strlen(a+1);/*长度*/ 120 bfs(); dp();/*失败标记跑一边,然后dp跑一边找答案*/ 121 printf("Case %d: %d\n",t,ans); 122 } 123 return 0; 124 }
(非注释版,最好就按照这个学习啦)
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<cstdlib> 5 #include<cmath> 6 #include<iostream> 7 using namespace std; 8 struct node 9 { 10 int s,fail,cnt[5]; 11 }tr[2010]; 12 int tot,list[2010]; 13 char a[2010]; 14 void clean(int x) 15 { 16 tr[x].fail=-1; tr[x].s=0; 17 memset(tr[x].cnt,-1,sizeof(tr[x].cnt)); 18 } 19 int id(char c) 20 { 21 if(c=='A') return 1; 22 if(c=='C') return 2; 23 if(c=='G') return 3; 24 return 4; 25 } 26 void build_tree() 27 { 28 int x=0; int len=strlen(a+1); 29 for(int i=1;i<=len;i++) 30 { 31 int y=id(a[i]); 32 if(tr[x].cnt[y]==-1) 33 { 34 tr[x].cnt[y]=++tot; 35 clean(tot); 36 } 37 x=tr[x].cnt[y]; 38 } 39 tr[x].s++; 40 } 41 void bfs() 42 { 43 list[0]=1; int head=1,tail=1; 44 tr[0].fail=-1; 45 while(head<=tail) 46 { 47 int x=list[head]; 48 for(int i=1;i<=4;i++) 49 { 50 int son=tr[x].cnt[i]; 51 if(son==-1) 52 { 53 if(x==0) tr[x].cnt[i]=0; 54 else tr[x].cnt[i]=tr[tr[x].fail].cnt[i]; 55 continue; 56 } 57 if(x==0) tr[son].fail=0; 58 else 59 { 60 int j=tr[x].fail; 61 while(j!=-1) 62 { 63 if(tr[j].cnt[i]!=-1) 64 { 65 tr[son].fail=tr[j].cnt[i]; 66 int tt=tr[j].cnt[i]; 67 if(tr[tt].s!=0) tr[son].s=1; 68 break; 69 } 70 j=tr[j].fail; 71 } 72 if(j==-1) tr[son].fail=0; 73 } 74 list[++tail]=son; 75 } 76 head++; 77 } 78 } 79 int f[2100][2100],p,n,ans; 80 void dp() 81 { 82 for(int i=0;i<=n;i++) for(int j=0;j<=tot;j++) f[i][j]=999999999; f[0][0]=0; 83 for(int i=0;i<n;i++) 84 { 85 for(int j=0;j<=tot;j++) 86 { 87 if(f[i][j]==999999999) continue; 88 for(int k=1;k<=4;k++) 89 { 90 int son=tr[j].cnt[k]; 91 if(tr[son].s) continue; 92 f[i+1][son]=min(f[i+1][son],f[i][j]+(id(a[i+1])!=k)); 93 } 94 } 95 } 96 ans=999999999; 97 for(int i=0;i<=tot;i++) ans=min(ans,f[n][i]); 98 if(ans==999999999) ans=-1; 99 } 100 int main() 101 { 102 int t=0; while(scanf("%d",&p)!=EOF && p) 103 { 104 t++; tot=0; clean(0); 105 for(int i=1;i<=p;i++) 106 { 107 scanf("%s",a+1); 108 build_tree(); 109 } 110 scanf("%s",a+1); n=strlen(a+1); 111 bfs(); dp(); 112 printf("Case %d: %d\n",t,ans); 113 } 114 return 0; 115 }
我们最终都要成长,最终都要和稚嫩的自己告别.