AC自动机练习2:修改串

这道题的话用到了dp,一个比较简单的dp方程

1466: 【AC自动机】修改串

poj3691

时间限制: 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
",t,ans);
122     }
123     return 0;
124 }
Tristan Code 注释版

(非注释版,最好就按照这个学习啦)

  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
",t,ans);
113     }
114     return 0;
115 }
Tristan Code 非注释版
原文地址:https://www.cnblogs.com/Tristanjiang/p/11374178.html