AC自动机之初学习

昨天开始搞ac自动机,其实搞完kmp和trie树之后搞ac自动机还是很简单的,主要是形成一套ac自动机的代码风格比较艰难,找了份简洁明了实用性强的代码分析了一天,以后就按这个风格写了。

hdu2896

模板题,借此把我加注释的模板贴出来~

#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>

using namespace std;

int next[100001][128];  //trie树
int fail[100001];   //失败指针,相当于kmp的next数组
int mark[100001];   //标记数组,记录节点信息
bool used[501];
int ans;
int root,sum;
int n,m;

int newnode()   //产生新的节点
{
    for (int i=0;i<128;i++)
        next[sum][i]=-1;
    mark[sum++]=-1;
    return sum-1;
}

void init()     //初始化
{
    sum=0;
    ans=0;
    root=newnode();
}

void insert(char * s,int id)    //构造trie树
{
    int len=strlen(s);
    int now=root;
    for (int i=0;i<len;i++)
    {
        if (next[now][s[i]]==-1)
            next[now][s[i]]=newnode();
        now=next[now][s[i]];
    }
    mark[now]=id;
}

void build()    //构造fail指针
{
    queue <int> mq;
    fail[root]=root;
    for (int i=0;i<128;i++)
    {
        if (next[root][i]==-1)
            next[root][i]=root;     //如果根节点没有这个孩子,那么直接将next设为根节点,即假设匹配这个字符之后来到了根节点
                                    //其实是将这个字符视为不能匹配的字符
        else
        {
            fail[next[root][i]]=root;   //否则,这个点的fail指针指向根节点,即如果这个节点的孩子匹配失败,回到根节点重新匹配
            mq.push(next[root][i]);     //这个节点形成的树非空,入队
        }
    }
    while (!mq.empty())
    {
        int now=mq.front();
        mq.pop();
        for (int i=0;i<128;i++)
        {
            if (next[now][i]==-1)   //如果当前节点的孩子节点不存在,将这个节点的孩子节点视为这个节点匹配失败后来到的位置的相同
                                    //孩子节点,如果它的fail节点也没有这个孩子,会形成一条回到根节点的链
                next[now][i]=next[fail[now]][i];
            else        //否则,将这个孩子节点的fail节点设为now的的fail节点的相同孩子节点位置
            {
                fail[next[now][i]]=next[fail[now]][i];
                mq.push(next[now][i]);
            }
        }
    }
    /*
    这样形成next构成一个闭合的图,对每个节点的所有孩子节点都能出发,要么走到其真正孩子节点,要么其没有这个孩子,相当
    于失配,走到失配后来到的位置,如果走到根节点还失配,则形成循环,看着是一条链,其实由于每次都是记录的之前的信息,记录
    之后寻找下一个位置的花费是1,可以画出这个bfs递推过程体会一下
    这样形成的fail就是正常的fail指针了,即当前节点匹配下一节点失败时要去的节点,由于这里面跟next联系到一起,他们相互作用,使得
    任意一次查找的花费都是1,画图体会下一条链是怎样形成的是最好的理解方式
    */
}

void query(char * s,int id)
{
    int len=strlen(s);
    int now=root;
    bool flag=false;
    memset(used,false,sizeof(used));
    for (int i=0;i<len;i++)
    {
        now=next[now][s[i]];
        int temp=now;
        while (temp!=root)
        {
            if (mark[temp]!=-1)
            {
                used[mark[temp]]=true;
                flag=true;
            }
            temp=fail[temp];
        }
    }
    if (!flag)
        return ;
    ans++;
    printf("web %d:",id);
    for (int i=1;i<=n;i++)
        if (used[i])
            printf(" %d",i);
    printf("\n");
}

char ch[10005];

int main()
{
    while (~scanf("%d",&n))
    {
        init();
        for (int i=1;i<=n;i++)
        {
            scanf("%s",ch);
            insert(ch,i);
        }
        scanf("%d",&m);
        build();
        for (int i=1;i<=m;i++)
        {
            scanf("%s",ch);
            query(ch,i);
        }
        printf("total: %d\n",ans);
    }
}

hdu3689

好吧,其实学ac自动机是为了做这个题目,结果被折磨了整整八个小时才看懂,网上各种说水题简单题,可是不知道那些概率的递推是如何推出来和证明的根本看不到,感谢matrix67的文章 http://www.matrix67.com/blog/archives/366 ,让我最后算是看明白了这个概率究竟是怎样一步步推出来的,以及为什么两份代码一份加一份减却能输出相同的结果了。附两份代码,一份是kmp版的方法,一份是ac自动机的方法,关键代码都已注释,具体自己看代码。

KMP版:求出所有不满足题意的,用1去减便是结果

#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;

int next[15];
char ch[15];
int n,m;
double p[26];
double dp[1010][15];    //当前走了i步,匹配长度为j
int f[1010][26];

void getnext()
{
    memset(f,0,sizeof(f));
    int len=strlen(ch+1);
    for (int i=2;i<=len;i++)
    {
        int j=next[i-1];
        while (j>0&&ch[j+1]!=ch[i])
            j=next[j];
        if (ch[j+1]==ch[i])
            j++;
        next[i]=j;
    }
    for (int i=0;i<=len;i++)
    {
        for (int t=0;t<26;t++)
        {
            if (p[t]>0)
            {
                int j=i;
                while (j>0&&ch[j+1]-'a'!=t)
                    j=next[j];
                if (ch[j+1]-'a'==t)
                    j++;
                f[i][t]=j;
            }
        }
    }
}

void DP()
{
    memset(dp,0,sizeof(dp));
    dp[0][0]=1;             //第0步匹配成功0个的概率为1
    int len=strlen(ch+1);
    for (int i=0;i<m;i++)   //枚举第几步
    {
        for (int t=0;t<len;t++)     //枚举匹配成功的长度,长度为len的不能继续递推,递推会造成重复计数
        {
            for (int k=0;k<26;k++)      //枚举下一步走向k字母
            {
                if (p[k]>0)     //f[t][k]代表下一步走向k时来到的新的匹配长度
                {
                    dp[i+1][f[t][k]]+=dp[i][t]*p[k];
                }
            }
        }
    }
    double ans=0;
    //走到m步仍然没有匹配成功的结果加起来便是不能匹配成功的概率,所有能匹配成功的结果
    //都没继续递推,所以走到m的时候没有匹配成功便代表一直没有匹配成功
    for (int i=0;i<len;i++)
        ans+=dp[m][i];
    printf("%.2f%%\n",100-ans*100);     //由于这一列的结果相加肯定为1(要么匹配成功过,要么没有),所以减去没有匹配成功的便是答案
}

int main()
{
    while (cin>>n>>m&&n+m)
    {
        memset(p,0,sizeof(p));
        memset(next,0,sizeof(next));
        for (int i=1;i<=n;i++)
        {
            char a;
            double b;
            cin>>a>>b;
            p[a-'a']=b;
        }
        scanf("%s",ch+1);
        next[0]=next[1]=0;
        getnext();
        DP();
    }
}

ac自动机版:求出所有满足题意的结果,相加
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>

using namespace std;

int next[1010][26];
int fail[1010];
int sum,root;
int n,m;
double dp[1010][15];    //当前步数为i,匹配的节点编号为j(其实根节点是0,其余节点依次类推,自动机只有一条链,故也能理解为匹配长度)
double p[26];
char ch[15];

int newnode()
{
    for (int i=0;i<26;i++)
        next[sum][i]=-1;
    sum++;
    return sum-1;
}

void init()
{
    sum=0;
    root=newnode();
}

void insert(char * s)
{
    int now=root;
    int len=strlen(s);
    for (int i=0;i<len;i++)
    {
        int id=s[i]-'a';
        if (next[now][id]==-1)
            next[now][id]=newnode();
        now=next[now][id];
    }
}

void build()
{
    queue <int> mq;
    fail[root]=0;
    for (int i=0;i<26;i++)
    {
        if (next[root][i]==-1)
            next[root][i]=root;     
        else
        {
            fail[next[root][i]]=root;
            mq.push(next[root][i]);
        }
    }
    while (!mq.empty())
    {
        int now=mq.front();
        mq.pop();
        for (int i=0;i<26;i++)
        {
            if (next[now][i]==-1)
                next[now][i]=next[fail[now]][i];
            else
            {
                fail[next[now][i]]=next[fail[now]][i];
                mq.push(next[now][i]);
            }
        }
    }
}

int main()
{
    while (cin>>n>>m&&n+m)
    {
        memset(dp,0,sizeof(dp));
        memset(p,0,sizeof(p));
        memset(next,0,sizeof(next));
        memset(fail,0,sizeof(fail));
        for (int i=1;i<=n;i++)
        {
            char a;
            double b;
            cin>>a>>b;
            int id=a-'a';
            p[id]=b;
        }
        init();
        scanf("%s",ch);
        insert(ch);
        build();
        int len=strlen(ch);
        dp[0][0]=1;     //第0步走到根节点的概率为1
        for (int i=0;i<m;i++)
        {
            for (int t=0;t<26;t++)
            {
                for (int k=0;k<sum-1;k++)   //一共sum-1个节点,一旦推理到sum-1即发现了一个可行串,便不能向下推理了(防止重复)
                {
                    int v=next[k][t];   //从k节点出发走向t节点
                    dp[i+1][v]+=dp[i][k]*p[t];  //将这种情况形成的概率加到下一步中
                }
            }
        }
        double ans=0;
        for(int i=0; i<=m; i++)     //在每步形成可行串的概率加起来便是总的形成可行串的概率
            ans+=dp[i][sum-1];
        printf("%.2f%%\n",ans*100);
    }
}

hdu2457

又是一道ac自动机加dp的题目,花了一晚上时间才写出来,还是不熟悉用ac自动机完成树形dp。。。

这道题让求改变某些点使得不含病毒串的最少改变步数,那么对目标串进行dp,对于目标串上的每个节点都有两种选择,要么改变要么不改变,然后,对每一步,枚举所有节点,从这个节点出发到其子节点作为待更新节点,看从这个节点出发能否使得下一步到待更新节点代表的状态更优,如果待更新节点和当前步数匹配的节点相同,则表示不改变这个节点,不同则表示改变这个节点花费加1,然后看步数为len时在哪个状态花费最小,输出即可。

附代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <map>
#include <cstdlib>

using namespace std;

int next[1010][4];
int fail[1010];
int mark[1010];
int sum,root;
map <char,int> mp;
int n,m;
char ch[1010];
int dp[1010][1010];     //当前匹配的长度为i,处于t状态,注意只需要匹配长度为len即匹配完成,由于值能变,所以在哪个位置不重要
const int INF=0x3f3f3f3f;

int newnode()
{
    for (int i=0;i<4;i++)
        next[sum][i]=-1;
    mark[sum++]=-1;
    return sum-1;
}

void init()
{
    sum=0;
    root=newnode();
}

void insert(char * s)
{
    int now=root;
    int len=strlen(s);
    for (int i=0;i<len;i++)
    {
        int id=mp[s[i]];
        if (next[now][id]==-1)
            next[now][id]=newnode();
        now=next[now][id];
    }
    mark[now]=1;
}

void build()
{
    queue <int> mq;
    fail[root]=-1;
    for (int i=0;i<4;i++)
    {
        if (next[root][i]==-1)
            next[root][i]=root;
        else
        {
            fail[next[root][i]]=root;
            mq.push(next[root][i]);
        }
    }
    while (!mq.empty())
    {
        int now=mq.front();
        mq.pop();
        for (int i=0;i<4;i++)
        {
            if (next[now][i]==-1)
                next[now][i]=next[fail[now]][i];
            else
            {
                fail[next[now][i]]=next[fail[now]][i];
                mq.push(next[now][i]);
            }
        }
    }
}

int min(int a,int b)
{
    return a>b?b:a;
}

void solve(char * s,int ca)
{
    int len=strlen(s);
    for (int i=0;i<=1000;i++)   //初始化所有节点花费都为无穷大
        for (int t=0;t<sum;t++)
            dp[i][t]=INF;
    dp[0][0]=0;     //当前匹配长度为0,在状态0,即根节点。
    int now=root;
    for (int i=0;i<len;i++)     //只需要匹配到长度为len即可,由len-1推出len
    {
        int id=mp[s[i]];
        for (int t=0;t<4;t++)       //当前待更新状态为next{k}[t]
        {
            for (int k=0;k<sum;k++)     //通过k状态推过来,注意对于自动机来说,每个节点便是一个状态
            {
                int temp=next[k][t];
                while (temp!=-1)        //遍历当前串所有后缀
                {
                    if (mark[temp]==1)  //如果为病毒串,跳出
                        break;
                    temp=fail[temp];
                }
                if (temp!=-1)       //证明当前串的某个后缀为病毒串,不可用
                    continue ;
                else
                {
                    int p=0;
                    if (t!=id)      //如果当前串的末尾和待匹配位置的值相同,则代表不改变,否则代表改变,花费+1
                        p=1;
                    dp[i+1][next[k][t]]=min(dp[i+1][next[k][t]],dp[i][k]+p);
                }
            }
        }
    }
    int ans=INF;
    for (int i=0;i<sum;i++)
        ans=min(ans,dp[len][i]);
    if (ans>=INF)
        ans=-1;
    printf("Case %d: %d\n",ca,ans);
}

int main()
{
    mp['A']=0;
    mp['G']=1;
    mp['C']=2;
    mp['T']=3;
    int ca=1;
    while (cin>>n&&n)
    {
        init();
        for (int i=0;i<n;i++)
        {
            scanf("%s",ch);
            insert(ch);
        }
        scanf("%s",ch);
        build();
        solve(ch,ca);
        ca++;
    }
}


标题SpringBoot智能在线预约挂号系统研究AI更换标题第1章引言介绍智能在线预约挂号系统的研究背景、意义、国内外研究现状及论文创新点。1.1研究背景与意义阐述智能在线预约挂号系统对提升医疗服务效率的重要性。1.2国内外研究现状分析国内外智能在线预约挂号系统的研究与应用情况。1.3研究方法及创新点概述本文采用的技术路线、研究方法及主要创新点。第2章相关理论总结智能在线预约挂号系统相关理论,包括系统架构、开发技术等。2.1系统架构设计理论介绍系统架构设计的基本原则和常用方法。2.2SpringBoot开发框架理论阐述SpringBoot框架的特点、优势及其在系统开发中的应用。2.3数据库设计与管理理论介绍数据库设计原则、数据模型及数据库管理系统。2.4网络安全与数据保护理论讨论网络安全威胁、数据保护技术及其在系统中的应用。第3章SpringBoot智能在线预约挂号系统设计详细介绍系统的设计方案,包括功能模块划分、数据库设计等。3.1系统功能模块设计划分系统功能模块,如用户管理、挂号管理、医生排班等。3.2数据库设计与实现设计数据库表结构,确定字段类型、主键及外键关系。3.3用户界面设计设计用户友好的界面,提升用户体验。3.4系统安全设计阐述系统安全策略,包括用户认证、数据加密等。第4章系统实现与测试介绍系统的实现过程,包括编码、测试及优化等。4.1系统编码实现采用SpringBoot框架进行系统编码实现。4.2系统测试方法介绍系统测试的方法、步骤及测试用例设计。4.3系统性能测试与分析对系统进行性能测试,分析测试结果并提出优化建议。4.4系统优化与改进根据测试结果对系统进行优化和改进,提升系统性能。第5章研究结果呈现系统实现后的效果,包括功能实现、性能提升等。5.1系统功能实现效果展示系统各功能模块的实现效果,如挂号成功界面等。5.2系统性能提升效果对比优化前后的系统性能
在金融行业中,对信用风险的判断是核心环节之一,其结果对机构的信贷政策和风险控制策略有直接影响。本文将围绕如何借助机器学习方法,尤其是Sklearn工具包,建立用于判断信用状况的预测系统。文中将涵盖逻辑回归、支持向量机等常见方法,并通过实际操作流程进行说明。 一、机器学习基本概念 机器学习属于人工智能的子领域,其基本理念是通过数据自动学习规律,而非依赖人工设定规则。在信贷分析中,该技术可用于挖掘历史数据中的潜在规律,进而对未来的信用表现进行预测。 二、Sklearn工具包概述 Sklearn(Scikit-learn)是Python语言中广泛使用的机器学习模块,提供多种数据处理和建模功能。它简化了数据清洗、特征提取、模型构建、验证与优化等流程,是数据科学项目中的常用工具。 三、逻辑回归模型 逻辑回归是一种常用于分类任务的线性模型,特别适用于二类问题。在信用评估中,该模型可用于判断借款人是否可能违约。其通过逻辑函数将输出映射为0到1之间的概率值,从而表示违约的可能性。 四、支持向量机模型 支持向量机是一种用于监督学习的算法,适用于数据维度高、样本量小的情况。在信用分析中,该方法能够通过寻找最佳分割面,区分违约与非违约客户。通过选用不同核函数,可应对复杂的非线性关系,提升预测精度。 五、数据预处理步骤 在建模前,需对原始数据进行清理与转换,包括处理缺失值、识别异常点、标准化数值、筛选有效特征等。对于信用评分,常见的输入变量包括收入水平、负债比例、信用历史记录、职业稳定性等。预处理有助于减少噪声干扰,增强模型的适应性。 六、模型构建与验证 借助Sklearn,可以将数据集划分为训练集和测试集,并通过交叉验证调整参数以提升模型性能。常用评估指标包括准确率、召回率、F1值以及AUC-ROC曲线。在处理不平衡数据时,更应关注模型的召回率与特异性。 七、集成学习方法 为提升模型预测能力,可采用集成策略,如结合多个模型的预测结果。这有助于降低单一模型的偏差与方差,增强整体预测的稳定性与准确性。 综上,基于机器学习的信用评估系统可通过Sklearn中的多种算法,结合合理的数据处理与模型优化,实现对借款人信用状况的精准判断。在实际应用中,需持续调整模型以适应市场变化,保障预测结果的长期有效性。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值