AC自动机练习题(uva 1149 uva 11468)

这两天又刷了两道AC自动机的题:突然发现uva上对数组越界的判断居然是TLE(T_T惨痛的经历,希望以后不会有人和我一样惨)。

 还有就是ac自动机裸考的题真的不多,都是与其他的算法结合到一起的。

第一道 uva  11468

 大致题意:给出一些字符和各自对应的选择概率,随机选择L次后将得到一个长度为L的随机字符串S(每次独立随机)。给出K个模板串,计算S不包含任何一个串的概率(即任何一个模板串都不是S的连续子串)。

题解:将所有的模板生成一个ac自动机,并且将哪些虚拟边与真实边一视同仁;在代码中的修改就是将上回我给出的模板中的if (!u)continue;改为 if (!u){ch[r][c]=ch[f[r]][c];continue;} 这样我们就不需要失配函数了,不用维护val与last数组了。

            建立完上述ac自动机后(你也可以把上述ac自动机看成一个图),我们从0节点走,并且把所有单词节点标记为“禁止”。于是就变成了求从节点0开始走L步,不进入任何禁止的节点的概率。这个可以用记忆化搜索的方法求得,设d(i,j)表示当前在节点i,还需要走j步,不碰到任何禁止节点的概率,代码如下:

double getProb(int u,int L){
    if (!L) return 1.0;
    if (vis[u][L]) return d[u][L];
    vis[u][L]=true;
    double &ans=d[u][L];
    ans=0.0;
    for (int i=0;i<sigma_size;i++){
      if (!val[ch[u][i]]) {
      //    printf("%d %d %d\n",u,i,val[ch[u][i]]);
          ans+=prob[i]*getProb(ch[u][i],L-1);}
    }
    //printf("u=%d L=%d %lf\n",u,L,ans);
    return ans;
  }

我的代码:

#include <cstdio>
#include <cstring>
#include <queue>
const int maxnode= 20*20+100;
const int sigma_size=64;
using namespace std;
double prob[sigma_size];
int idx(char c){
  if (c>='a'&&c<='z') return c-'a';
  if (c>='A'&&c<='Z') return c-'A'+26;
  if (c>='0'&&c<='9') return c-'0'+52;


}
struct AC{
  int ch[maxnode][sigma_size];
  int val[maxnode];
  int fail[maxnode];
  bool vis [maxnode][110];
  double d[maxnode][110];
  int sz;
  void clear(){sz=1;memset(ch[0],0,sizeof(ch[0]));memset(vis,0,sizeof(vis));memset(prob,0,sizeof(prob));}
  void insert(char *s,int v){
    int u=0,n=strlen(s);
    for (int i=0;i<n;i++){
      int c=idx(s[i]);
      //printf("sz=%d\n",sz);
      if (!ch[u][c]){
        memset(ch[sz],0,sizeof(ch[sz]));
        val[sz]=0;
        ch[u][c]=sz++;
        //printf("sz=%d*\n",sz);
      }
      u=ch[u][c];
    }
    val[u]=v;
  }
  void getFail(){
    queue <int> qqq;
    fail[0]=0;
    for (int i=0;i<sigma_size;i++){
      if (ch[0][i]) {qqq.push(ch[0][i]);fail[ch[0][i]]=0;}
    }
    while (!qqq.empty()){
      int r=qqq.front();qqq.pop();
      for (int c=0;c<sigma_size;c++){
        int u=ch[r][c];
        if (!u){ch[r][c]=ch[fail[r]][c];continue;}
        qqq.push(u);
        int v=fail[r];
        while (v&&!ch[v][c]) v=fail[v];
        fail[u]=ch[v][c];
        val[u]|=val[fail[u]];
      }
    }
  }
  double getProb(int u,int L){
    if (!L) return 1.0;
    if (vis[u][L]) return d[u][L];
    vis[u][L]=true;
    double &ans=d[u][L];
    ans=0.0;
    for (int i=0;i<sigma_size;i++){
      if (!val[ch[u][i]]) {
          ans+=prob[i]*getProb(ch[u][i],L-1);}
    }
    return ans;
  }
};
struct AC ac;
int main (){
  int T,kase,l;scanf("%d",&T);
  for (kase=1;kase<=T;kase++){
    ac.clear();
    int n,m;scanf("%d",&n);
    for (int i=0;i<n;i++) {
      char s[30];scanf("%s",s);
      ac.insert(s,1);
    }
    scanf("%d",&m);
    for (int i=0;i<m;i++){
      char c[2];scanf("%s",c);
      scanf("%lf",&prob[idx(c[0])]);
    }
    ac.getFail();
    scanf("%d",&l);
    printf("Case #%d: %.6lf\n",kase,ac.getProb(0,l));
  }
  return 0;
}

第二道   uva 11019

  题目大意: 给出一个n*m的字符矩阵T,你的任务是找出给定的x*y的字符矩阵P出现了多少次。即需要在二维文本串T中查找二维模板串P。

  解题思路:将给定的P矩阵,每一行为一个字符串建立一个ac自动机,然后在遍历字符矩阵T的每一行寻找所有的匹配点。最后我们建立一个count数组,count[r][c]表示T中以(r,c)为左上角,与P等大的矩阵中有多少个完整的行与P对应位置的行完全相同。

                    当遍历完T矩阵后,计算count[r][c]==x(x为P的行数)的个数,即为我们所求。

我的代码:

#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <map>
#include <string>
const int maxnode = 100*100+100;
const int sigma_size =26;
using namespace std;
char s[1010][1010];
char pp[110][110];
int count[1010][1010];
int t,n,m,x,y,eee;
map <string,int> nnn;
int next[110];
struct AC{
  int ch[maxnode][sigma_size];
  int val[maxnode];
  int f[maxnode];
  int last[maxnode];
  int sz;
  void clear(){
    sz=1;
    memset(ch[0],0,sizeof(ch[0]));
  }
  int idx(char c){
    return c-'a';
  }
  void insert(char *s,int v){
    int n=strlen(s),u=0;
    for (int i=0;i<n;i++){
      int c=idx(s[i]);
      if (!ch[u][c]){
         memset(ch[sz],0,sizeof(ch[sz]));
         val[sz]=0;
         ch[u][c]=sz++;
      }
      u=ch[u][c];
    }
    val[u]=v;
  }
  void getFail(){
    f[0]=0;
    queue <int> q;
    for (int i=0;i<sigma_size;i++){
      if (ch[0][i]) {
          f[ch[0][i]]=0;
          q.push(ch[0][i]);
          last[ch[0][i]]=0;
      }
    }
    while (!q.empty()){
      int r=q.front();q.pop();
      for (int c=0;c<sigma_size;c++){
        int u=ch[r][c];
        if (!u) continue;
        q.push(u);
        int v=f[r];
        while (v&&!ch[v][c]) v=f[v];
        f[u]=ch[v][c];
        last[u]=val[f[u]]?f[u]:last[f[u]];
      }
    }
  }
  void find(char *T){
    int n=strlen(T);
    int j=0;
    for (int i=0;i<n;i++){
      int c=idx(T[i]);
      while (j&&!ch[j][c]) j=f[j];
      j=ch[j][c];
    //  printf("i=%d j=%d val[%d]=%d\n",i,j,j,val[j]);
      if (val[j]) print(i,j);
      else if (last[j]) print(i,last[j]);
    }
  }
  void print(int i,int j){
    if (j) {
      int ppp=nnn[string(pp[val[j]])];
      while (ppp){
        if (eee-ppp+1>=0&&i-y+1>=0) count[eee-ppp+1][i-y+1]++;
        ppp=next[ppp];
      }
      //printf("x=%d y=%d eee=%d i=%d val[%d]=%d\n",eee-val[j]+1,i-y+1,eee,i,j,val[j]);
      print(i,last[j]);
    }
  }
};
AC ac;
int main (){
  scanf("%d",&t);
  while (t--){
    scanf("%d%d",&n,&m);
    memset(count,0,sizeof(count));
    memset(next,0,sizeof(next));
    nnn.clear();
    ac.clear();
    for (int i=0;i<n;i++)
      scanf("%s",s[i]);
    scanf("%d%d",&x,&y);
    for (int i=1;i<=x;i++){
      scanf("%s",pp[i]);
      if (nnn[string(pp[i])]==0){
        nnn[string(pp[i])]=i;
        ac.insert(pp[i],i);
      }
      else {
        next[i]=nnn[string(pp[i])];
        nnn[string(pp[i])]=i;
      }
    }
    ac.getFail();
    for (eee=0;eee<n;eee++){
      ac.find(s[eee]);
    }
    int ans=0;
   // for(int i=0;i<n;i++){
   //     for (int j=0;j<m;j++)
   //         printf("%d ",count[i][j]);
   //     printf("\n");
   // }
    for(int i=0;i<n-x+1;i++)
        for (int j=0;j<m-y+1;j++)
            if (count[i][j]==x) ans++;
    printf("%d\n",ans);
  }
  return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值