浅谈AC自动机

学习AC自动机之前还需要一点其他的知识的基础:KMPtrie树(字典树)。

首先我们要知道AC自动机是干什么用的。

大家都知道KMP算法是求单字符串对单字符串的匹配使用的。

那么,多个字符串在一个字符串上的匹配怎么办?

如果每次对一个需要匹配的串求一次next数组,然后一次次去匹配,显然,这样变得很慢很慢了。

那么,我们要如何解决这类问题呢?

就是AC自动机。

先看一下它的定义:Aho-Corasick automation,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法。

正式开始,我们来讲解AC自动机。

第一步:把它们构建成一棵Trie树。

第二步(也就是最重要的一步):构建失配指针。

这一步很KMP算法中的next数组是类似的,通过跳转来省略重复的检查。

我们先看看这个指针是要干什么吧。每次沿着Trie树匹配,匹配到当前位置没有匹配上时,直接跳转到失配指针所指向的位置继续进行匹配。

Trie树的失配指针是指向:沿着其父节点 的 失配指针,一直向上,直到找到拥有当前这个字母的子节点 的节点 的那个子节点。

感觉听起来很复杂吧,但是自己画一下图就好了。

值得一提的是,第二层的所有节点的失配指针,都要直接指向Trie树的根节点。

第三步:匹配。首先,指针指向根节点,依次读入单词,检查是否存在这个子节点,然后指针跳转到子节点,如果不存在,直接跳转到失配指针即可。

例题:HDU2222

题意:问文本串中用几个目标串出现。

Code:

#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<cstdio>
#include<queue>
#define N 1000005
using namespace std;
int cnt;
char s[N];
struct tree
{
    int fail,son[26],end;
}AC[N];
inline int read()
{
    int x=0,f=1;char s=getchar();
    while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
    while(s<='9'&&s>='0'){x=x*10+s-'0';s=getchar();}
    return x*f;
}
void insert(char s[])
{
    int len=strlen(s);
    int now=0;
    for(int i=0;i<len;i++)
    {
        if(!AC[now].son[s[i]-'a'])
            AC[now].son[s[i]-'a']=++cnt;
        now=AC[now].son[s[i]-'a'];
    }
    AC[now].end+=1;
}
void Get_fail()
{
    queue<int> Q;
    for(int i=0;i<=25;i++)
        if(AC[0].son[i])
        {
            AC[AC[0].son[i]].fail=0;
            Q.push(AC[0].son[i]);
        }
    while(!Q.empty())
    {
        int u=Q.front();
        Q.pop();
        for(int i=0;i<=25;i++)
            if(AC[u].son[i])
            {
                AC[AC[u].son[i]].fail=AC[AC[u].fail].son[i];
                Q.push(AC[u].son[i]);
            }else
                AC[u].son[i]=AC[AC[u].fail].son[i];
    }
}
int AC_Query(char s[]) 
{
    int len=strlen(s);
    int now=0,ans=0;
    for(int i=0;i<len;i++)
    {
        now=AC[now].son[s[i]-'a'];
        for(int t=now;t&&AC[t].end!=-1;t=AC[t].fail)
        {
            ans+=AC[t].end;
            AC[t].end=-1;
        }
    }
    return ans;
}
int main()
{
    int T=read();
    while(T--)
    {
        cnt=0;
        int n=read();
        memset(AC,0,sizeof(AC));
        for(int i=1;i<=n;i++)
        {
            scanf("%s",s);
            insert(s);
        }
        AC[0].fail=0;
        Get_fail();
        scanf("%s",s);
        printf("%d\n",AC_Query(s));
    }
    return 0;
}
例题: 洛谷3796

题意:求文本串中每个目标串最多出现几次。

Code:

#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<cstdio>
#include<queue>
#define N 1000005
using namespace std;
int cnt;
char s[N][105],ch[N];
struct tree
{
    int fail,son[26],end;
}AC[N];
struct node
{
    int num,pos;
}ans[N];
int cmp(node x,node y)
{
    if(x.num!=y.num)return x.num>y.num;
        else return x.pos<y.pos;
}
inline int read()
{
    int x=0,f=1;char s=getchar();
    while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
    while(s<='9'&&s>='0'){x=x*10+s-'0';s=getchar();}
    return x*f;
}
void insert(char s[],int num)
{
    int len=strlen(s);
    int now=0;
    for(int i=0;i<len;i++)
    {
        if(!AC[now].son[s[i]-'a'])
            AC[now].son[s[i]-'a']=++cnt;
        now=AC[now].son[s[i]-'a'];
    }
    AC[now].end=num;
}
void Get_fail()
{
    queue<int> Q;
    for(int i=0;i<=25;i++)
        if(AC[0].son[i])
        {
            AC[AC[0].son[i]].fail=0;
            Q.push(AC[0].son[i]);
        }
    while(!Q.empty())
    {
        int u=Q.front();
        Q.pop();
        for(int i=0;i<=25;i++)
            if(AC[u].son[i])
            {
                AC[AC[u].son[i]].fail=AC[AC[u].fail].son[i];
                Q.push(AC[u].son[i]);
            }else
                AC[u].son[i]=AC[AC[u].fail].son[i];
    }
}
void AC_Query(char s[]) 
{
    int len=strlen(s);
    int now=0;
    for(int i=0;i<len;i++)
    {
        now=AC[now].son[s[i]-'a'];
        for(int t=now;t;t=AC[t].fail)
            ans[AC[t].end].num++;
    }
}
int main()
{
    int n=read();
    while(n)
    {
        cnt=0;
        memset(AC,0,sizeof(AC));
        for(int i=1;i<=n;i++)
        {
            scanf("%s",s[i]);
            insert(s[i],i);
            ans[i].num=0;
            ans[i].pos=i;
        }
        AC[0].fail=0;
        Get_fail();
        scanf("%s",ch);
        AC_Query(ch);
        sort(ans+1,ans+1+n,cmp);
        printf("%d\n",ans[1].num);
        printf("%s\n",s[ans[1].pos]);
        for(int i=2;i<=n;i++) 
            if(ans[i].num==ans[i-1].num)
                printf("%s\n",s[ans[i].pos]);
                else break;
        n=read();
    }
    return 0;
}

内容概要:本文档详细介绍了基于Google Earth Engine (GEE) 构建的阿比让绿地分析仪表盘的设计与实现。首先,定义了研究区域的几何图形并将其可视化。接着,通过云掩膜函数和裁剪操作预处理Sentinel-2遥感影像,筛选出高质量的数据用于后续分析。然后,计算中值图像并提取NDVI(归一化差异植被指数),进而识别绿地及其面积。此外,还实现了多个高级分析功能,如多年变化趋势分析、人口-绿地交叉分析、城市热岛效应分析、生物多样性评估、交通可达性分析、城市扩张分析以及自动生成优化建议等。最后,提供了数据导出、移动端适配和报告生成功能,确保系统的实用性和便捷性。 适合人群:具备一定地理信息系统(GIS)和遥感基础知识的专业人士,如城市规划师、环境科学家、生态学家等。 使用场景及目标:①评估城市绿地分布及其变化趋势;②分析绿地与人口的关系,为城市规划提供依据;③研究城市热岛效应及生物多样性,支持环境保护决策;④评估交通可达性,优化城市交通网络;⑤监测城市扩张情况,辅助土地利用管理。 其他说明:该系统不仅提供了丰富的可视化工具,还集成了多种空间分析方法,能够帮助用户深入理解城市绿地的空间特征及其对环境和社会的影响。同时,系统支持移动端适配,方便随时随地进行分析。用户可以根据实际需求选择不同的分析模块,生成定制化的报告,为城市管理提供科学依据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JackflyDC

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值