AC自动机学习


1.  LA 4670 Dominating Patterns


题意: 在一个文本串中找出出现次数最多的模式串. 数据范围:  text_len <= 1000000 ,     word_len <= 70 ,    word_cnt <= 150

解法 : Aho-corasick (AC自动机)

说明: 与KMP算法类似 , 都是通过构造一个失配转移自动机 。 不同的是KMP直接在数组上操作, 而AC自动机是在字典树上操作。与KMP算法的比较及一些细节参见代码中说明。


白书模板:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int text_len = 1000050 , word_len = 75 , word_num = 155 ;
const int sigma_size = 26 , max_node = word_num * 80 ;

char word[word_num][word_len] , text[text_len] ;
int cnt[word_num] ;

int son[max_node][sigma_size] ,flag[max_node] , node_cnt ;
int f[max_node] , last[max_node];   // last 指向上个有flag标志的节点。 目的是简化失配过程。

void init(){
    node_cnt = 0;
    flag[0] = 0 ;
    memset(son[0] ,0 ,sizeof(son[0]));
}

inline int idx(char c) {return c - 'a' ; }

void Insert(char *s , int id){   // 字典树插入操作 。
    int n = strlen(s) , u = 0;
    for(int i = 0; i<n; i++){
        int v = idx(s[i]) ;
        if(!son[u][v]) {
            son[u][v] = ++ node_cnt ;
            flag[node_cnt] = 0 ;
            memset(son[node_cnt] , 0 ,sizeof(son[0])) ;
        }
        u = son[u][v] ;
    }
    flag[u] = id ;  // 做标记, 该模式串的标号是id
 }

void GetFail(){  // 计算失配函数 , 即构造状态转移图 。 是算法的重点!
    f[0] = last[0] = 0 ;   // 
    queue<int>Q ;
    for(int c = 0; c<sigma_size ;c++){
        int u = son[0][c] ;
        if(u) f[u] = last[u] = 0 , Q.push(u) ;   // 相当于KMP里 将f[1]置零 
    }
    while(!Q.empty()) {
        int r = Q.front() ; Q.pop() ;
        for(int c=0; c<sigma_size; c++) {
            int u = son[r][c] ;  // u 是 r 的下一个节点 .
            if(!u) {son[r][c] = son[f[r]][c]; continue ; } // 直接沿失配边跳到前面的节点。缩短失陪过程
            Q.push(u) ;
            int v = f[r] ;   // 由f[r]计算f[u] ,  相当于KMP里从f[i]算f[i+1]
            while(v && !son[v][c]) v = f[v] ;
            f[u] = son[v][c] ; 
            last[u] = flag[f[u]] ? f[u] : last[f[u]] ;
        }
    }
}

void Find(char *s){   // 查找操作
     memset(cnt ,0 ,sizeof(cnt));
     int n = strlen(s) ;
     int j = 0 ;
     for(int i=0; i<n; i++){
        int c = idx(s[i]) ;
        // while(j && !son[j][c]) j = f[j] ;
        j = son[j][c] ;
        int u = j;  // 即使这个节点处没有flag标志 , 也可能匹配到了另外某个模式串。 
        while(u){
            if(flag[u]) cnt[flag[u]] ++ ;
            u = last[u] ;
        }
     }
}

int main()
{
    int n ;
    while(scanf("%d" , &n)==1 && n){
        init() ;
        for(int i=1; i<=n; i++) scanf("%s" , word[i]) , Insert(word[i] , i) ; // 由于本题数据较弱,没有相同的模式串 ,故直接插入。 否则需要建立字符串到整数的映射。
        GetFail() ;
        scanf("%s" , text) ;
        Find(text) ;
        int M = 0 ;
        for(int i=1; i<=n; i++) M = max(M , cnt[i]) ;
        printf("%d\n" , M) ;
        for(int i=1; i<=n ;i++)
            if(cnt[i] == M) printf("%s\n" , word[i]) ;
    }
    return 0;
}

2.   UVA - 11468Substring 

需要改造AC自动机 , 将不能往下走的点接回失配点 。 并在自动机中标记所有的单词节点 ,注意存在下面这种情况:

2个单词 :     he    ,   sheil   

沿着 s  ->  h  -> e 走到这个节点时,同样是单词节点。

然后可以动态规划解决:  dp[i , j]  当前在节点i , 还需要走j 步 ,   转移是不经过单词节点,一步步往前走。

dp[ i , j ]    =     sum {  P(第x个字符第概率)   *   dp[ i 的第x个儿子 ] [ j-1]   }

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const double eps = 1e-6 ;
const int maxn = 25 , sigma_size = 62 ;

char word[maxn][maxn] ;
double prob[sigma_size] ;
bool notzero[sigma_size] ;
int son[maxn*20][sigma_size] , f[maxn*20] ,  is_word[maxn*20] , tot ;
double dp[maxn*20][110] ;
bool vis[maxn*20][110] ;

int new_node(){
    ++tot ;
    is_word[tot] = 0 ;
    memset(son[tot] ,0 ,sizeof(son[0]));
    return tot ;
}
inline int idx(char c){
    if('a' <=c && c<='z') return c - 'a' ;
    else if('A' <=c && c<='Z') return c - 'A' + 26 ;
    else return c - '0' + 52 ;
}
void init(){
    tot = 0 ;
    memset(notzero ,0 ,sizeof(notzero)) ;
    memset(prob ,0 ,sizeof(prob)) ;
    f[0] = is_word[0] = 0;
    memset(son[0] , 0 ,sizeof(son[0])) ;
}
void Insert(char *s) {
    int u = 0 , n = strlen(s) ;
    for(int i=0; i<n; i++){
        int c = idx(s[i]) ;
        if(!son[u][c]) son[u][c] = new_node() ;
        u = son[u][c] ;
    }
    is_word[u] = 1 ;
}
void GetFail(){
    f[0] = 0 ;
    queue<int>Q ;
    for(int c=0 ;c<sigma_size; c++){
        int u = son[0][c] ;
        if(u) Q.push(u) , f[u] = 0;  // if(u) cout<<" debug "<< u<<endl ;
    }
    while(!Q.empty()) {
        int r = Q.front() ; Q.pop() ;
        for(int c =0 ; c <sigma_size ;c++){
            int u = son[r][c] ;                   //  cout<<"now is " << u<<endl ;
            if(!u) {son[r][c] = son[f[r]][c] ; continue ; }
            Q.push(u);
            int v = f[r] ;
            while(v && !son[v][c]) v = f[v] ;
            f[u] = son[v][c] ;
            is_word[u]  |= is_word[f[u]] ;   //  cout<<"debug: "<<u<<"  "<<is_word[u]<<endl ;
        }
    }
}

double DP(int u , int L){
    if(vis[u][L]) return dp[u][L];
    if(L == 0 ) return dp[u][L] = 1.0 ;
    vis[u][L] = 1 ;
    double &ans = dp[u][L] ;
    ans = 0.0 ;
    for(int i=0; i<sigma_size ; i++){
        if(!is_word[ son[u][i] ] && notzero[i]) ans += prob[i] * DP(son[u][i] , L-1) ;
    }
    return ans ;
}

int main()
{
   // freopen("in.txt","r",stdin);
    int T , cas = 1 ;
    scanf("%d" ,&T) ;
    while(T--){
        int n ;
        init() ;
        scanf("%d" ,&n);
        for(int i=0; i<n ;i++){
            scanf("%s" , word[i]) ;
            Insert(word[i]) ;
        }
        GetFail() ;

        char ch[5] ;
        double pt ;
        scanf("%d" ,&n) ;
        for(int i=0; i<n;i++){
            scanf("%s%lf" , ch , &pt) ;
            notzero[idx(ch[0])] = 1;
            prob[idx(ch[0])] = pt ;
        }
        memset(vis , 0 ,sizeof(vis)) ;
        int L ;
        scanf("%d" ,&L) ;
        double ans = DP(0 , L) ;
        printf("Case #%d: %f\n" , cas++ , ans) ;
    }
    return 0;
}


3.  UVA - 11019  Matrix Matcher

二维的模式匹配 , 求其一个大的字符矩阵T内有多少个小的字符矩阵P。

方法: 将二位模式匹配转换为一维的模式匹配,然后就能利用一维的AC自动机等方法解决。

将模式矩阵P的每行当成一个字符串构建自动机。
依次匹配T的每行, 用cnt[ i ][ j ] 记录以i,j为左上顶点 且与 P大小相同的矩形框内能匹配到P的多少行。

明显cnt[ ][ ]   >=  P的行数为一个二维的匹配。

注意: P可以出现相同的两行。

有大神居然hash解决了。。。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int maxn = 1010 , sigma_size = 26 ;

int N , M , X , Y ;

char text[maxn][maxn] , word[110][110] ;
int son[100*100][100] ,f[100*100], num[100*100] ,row[100*100][100] ,last[100*100], tot ;
int cnt[maxn][maxn] ;

inline int idx(char c) {return c - 'a'; }
void ac_init(){
    tot = 0;
    memset(son[0] , 0, sizeof(son[0]));
    num[0] = 0;
}
int new_node(){
    ++tot ;
    memset(son[tot] , 0, sizeof(son[0]));
    num[tot] = 0;
    return tot ;
}
void Insert(char *s , int n , int id) {
    int u = 0 ;
    for(int i=0; i<n; i++){
        int c = idx(s[i]) ;
        if(!son[u][c]) son[u][c] = new_node() ;
        u = son[u][c] ;
    }
    row[u][ num[u]++ ] = id;
}
void GetFail(){
    last[0] = f[0] = 0;
    queue<int>Q ;
    for(int i=0 ; i<sigma_size ;i++){
        int u = son[0][i] ;
        if(u) Q.push(u) , f[u] = 0 , last[u] = 0;
    }
    while(!Q.empty()) {
        int r = Q.front(); Q.pop() ;
        for(int c = 0 ;c<sigma_size ;c++) {
            int u = son[r][c] ;
            if(!u) {son[r][c] = son[ f[r] ][c] ;  continue ;}
            Q.push(u) ;
            int v = f[r] ;
            while(v && !son[v][c]) v = f[v] ;
            f[u] = son[v][c] ;
            last[u] = num[f[u]] ? f[u] : last[f[u]] ;
        }
    }
}
void Match(char *s , int n ,int r){
    int u = 0;
    for(int i = 0; i<n; i++) {
        int c = idx(s[i]) ;
        u = son[u][c] ;
        int p =  u;
        while(p) {
            if(num[p]) {
                for(int k=0; k<num[p] ;k++) {
                    int rr = row[p][k] ;
                    if(r >= rr) cnt[r-rr][i-Y+1] ++;
                }
            }
            p = last[p] ;
        }
    }
}

int main()
{
  //  freopen("in.txt","r",stdin);
    int T ;
    scanf("%d" ,&T) ;
    while(T--){
        scanf("%d%d" ,&N ,&M) ;
        ac_init() ;
        for(int i=0; i<N ;i++) {
            scanf("%s" , text[i]) ;
        }
        scanf("%d%d" ,&X ,&Y) ;
        for(int i=0; i<X ;i++) {
            scanf("%s" , word[i]) ;
            Insert(word[i] , Y , i) ;
        }
        GetFail() ;
        memset(cnt , 0 ,sizeof(cnt)) ;
        for(int i=0; i<N; i++)
            Match(text[i] , M , i) ;
        int sum = 0;
        for(int i=0; i<N-X+1; i++)
            for(int j=0; j<M-Y+1;j++)
                if(cnt[i][j] == X) sum++;
        printf("%d\n" , sum) ;
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python AC自动机是一个用于字符串匹配的算法,它可以高效地在一段文本中查找多个预定义的模式。它的实现可以使用多种库,其中包括ac自动机python和ahocorasick-python。 ac自动机python是一个对标准的ac自动机算法进行了完善和优化的实现,适用于主流的Python发行版,包括Python2和Python3。它提供了更准确的结果,并且可以通过pip进行安装,具体的安装方法可以参考官方文档或者使用pip install命令进行安装。 ahocorasick-python是另一个实现AC自动机的库,它也可以用于Python2和Python3。你可以通过官方网站或者GitHub源码获取更多关于该库的信息和安装指南。 对于AC自动机的使用,一个常见的例子是在一段包含m个字符的文章中查找n个单词出现的次数。要了解AC自动机,需要有关于模式树(字典树)Trie和KMP模式匹配算法的基础知识。AC自动机的算法包括三个步骤:构造一棵Trie树,构造失败指针和模式匹配过程。在构造好AC自动机后,可以使用它来快速地在文本中查找预定义的模式,并统计它们的出现次数。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [ahocorasick-python:AC自动机python的实现,并进行了优化。 主要修复了 查询不准确的问题](https://download.csdn.net/download/weixin_42122986/18825869)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Python实现多模匹配——AC自动机](https://blog.csdn.net/zichen_ziqi/article/details/104246446)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值