[NOI2011]阿狸的打字机

本文介绍了一种基于Fail树的算法,用于解决在特定打字机模型下,查询一个子串在另一个子串中出现次数的问题。通过构建Trie树和其fail指针,实现对字符串匹配的高效查询。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

打字机上只有28个按键,分别印有26个小写英文字母和'B'、'P'两个字母。经阿狸研究发现,这个打字机是这样工作的:

·输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后)。

·按一下印有'B'的按键,打字机凹槽中最后一个字母会消失。

·按一下印有'P'的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失。

例如,阿狸输入aPaPBbP,纸上被打印的字符如下:

a aa ab 我们把纸上打印出来的字符串从1开始顺序编号,一直到n。打字机有一个非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数(x,y)(其中1≤x,y≤n),打字机会显示第x个打印的字符串在第y个打印的字符串中出现了多少次。

阿狸发现了这个功能以后很兴奋,他想写个程序完成同样的功能,你能帮助他么?

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~·

这道题是fail树的裸题。利用ac自动机或trie图能构建出trie树上的fail指针,代表与其拥有最长相同后缀的、比他短的位置。

重点是每个点只有唯一的fail指针

这就很像树了。(每个点只有唯一的父节点)

所以我们可以将fail指针反指,形成一棵树,叫fail树

fail树所具有的性质就是:当前子树中所有点代表的前缀都以当前树根代表的字符串为后缀。

人话翻译:

假设每个点对应一个字符串,那么若b点在a点的子树中,a一定是b的后缀。

现在再来看这道题,问题是求一个子串中有多少个另一个子串,就是问这个子串中的所有前缀以另一串为后缀的有多少个。

于是上fail树:

#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 100050
int n,m,len,tot,rt[N],hed[N],cnt;
char s[N];
struct Trie
{
    int ch[28];
    int fa,fl;
}tr[N];
struct EG
{
    int to,nxt;
}e[N];
void ae(int f,int t)
{
    e[++cnt].to = t;
    e[cnt].nxt = hed[f];
    hed[f] = cnt;
}
void trie_pic()
{
    queue<int>q;
    for(int i=1;i<=26;i++)
        if(tr[0].ch[i])
            q.push(tr[0].ch[i]),ae(0,tr[0].ch[i]);
    while(!q.empty())
    {
        int u = q.front();
        q.pop();
        for(int i=1;i<=26;i++)
        {
            int &v = tr[u].ch[i];
            if(!v)
            {
                v = tr[tr[u].fl].ch[i];
                continue;
            }
            tr[v].fl = tr[tr[u].fl].ch[i];
            ae(tr[v].fl,v);
            q.push(v);
        }
    }
}
int tin[N],tout[N],tim;
void dfs(int u)
{
    tin[u]=++tim;
    for(int j=hed[u];j;j=e[j].nxt)dfs(e[j].to);
    tout[u]=tim;
}
int f[2*N];
void up(int x,int d)
{
    if(!x)return ;
    while(x<=200000)
    {
        f[x]+=d;
        x+=(x&(-x));
    }
}
int down(int x)
{
    if(!x)return 0;
    int ret = 0;
    while(x)
    {
        ret+=f[x];
        x-=(x&(-x));
    }
    return ret;
}
struct node
{
    int u,v,id;
}p[N];
int ans[N];
bool cmp(node a,node b)
{
    return a.v<b.v;
}
int main()
{
    scanf("%s",s+1);
    len = strlen(s+1);
    int u = 0;
    for(int i=1;i<=len;i++)
    {
        if(s[i]>='a'&&s[i]<='z')
        {
            int c = s[i]-'a'+1;
            if(!tr[u].ch[c])tr[u].ch[c]=++tot,tr[tot].fa=u;
            u=tr[u].ch[c];
        }else if(s[i]=='B')
        {
            u = tr[u].fa;
        }else
        {
            rt[++n] = u;
        }
    }
    trie_pic();
    dfs(0);
    scanf("%d",&m);
    for(int i=1;i<=m;i++)scanf("%d%d",&p[i].u,&p[i].v),p[i].id=i;
    sort(p+1,p+1+m,cmp);
    int k = 1,c = 0;
    u = 0;
    for(int i=1;i<=len;i++)
    {
        if(s[i]>='a'&&s[i]<='z')
        {
            int c = s[i]-'a'+1;
            u = tr[u].ch[c];
            up(tin[u],1);
        }else if(s[i]=='B')
        {
            up(tin[u],-1);
            u = tr[u].fa;
        }else
        {
            c++;
            while(p[k].v==c)
            {
                ans[p[k].id] = down(tout[rt[p[k].u]])-down(tin[rt[p[k].u]]-1);
                k++;
            }
        }
    }
    for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
    return 0;
}

 

转载于:https://www.cnblogs.com/LiGuanlin1124/p/9676086.html

内容概要:本文档详细介绍了基于MATLAB实现的多头长短期记忆网络(MH-LSTM)结合Transformer编码器进行多变量时间序列预测的项目实例。项目旨在通过融合MH-LSTM对时序动态的细致学习和Transformer对全局依赖的捕捉,显著提升多变量时间序列预测的精度和稳定性。文档涵盖了从项目背景、目标意义、挑战与解决方案、模型架构及代码示例,到具体的应用领域、部署与应用、未来改进方向等方面的全面内容。项目不仅展示了技术实现细节,还提供了从数据预处理、模型构建与训练到性能评估的全流程指导。 适合人群:具备一定编程基础,特别是熟悉MATLAB和深度学习基础知识的研发人员、数据科学家以及从事时间序列预测研究的专业人士。 使用场景及目标:①深入理解MH-LSTM与Transformer结合的多变量时间序列预测模型原理;②掌握MATLAB环境下复杂神经网络的搭建、训练及优化技巧;③应用于金融风险管理、智能电网负荷预测、气象预报、交通流量预测、工业设备健康监测、医疗数据分析、供应链需求预测等多个实际场景,以提高预测精度和决策质量。 阅读建议:此资源不仅适用于希望深入了解多变量时间序列预测技术的读者,也适合希望通过MATLAB实现复杂深度学习模型的开发者。建议读者在学习过程中结合提供的代码示例进行实践操作,并关注模型训练中的关键步骤和超参数调优策略,以便更好地应用于实际项目中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值