序列自动机 浅谈

其实这个东西真的算自动机吗?好像还真的符合自动机的定义啊;

我将在下面用人话来定义序列自动机,并不像某度某科一样不说人话;

设一个字符集S,nxt[i][j]表示第i个位置往后第一个j元素出现的位置;

这个nxt数组可以O(n)的求出来,可以自行验证;

for(int i=n-1;i>=0;--i){
    for(int j=1;j<=26;++j) nxt[i][j]=nxt[i+1][j];
    nxt[i][s[i+1]-‘a‘+1]=i+1;
}

我们会发现一个神奇的事情:这是一个DAG!

她能干什么事情呢?

1.判断是否是原字符串的子序列

当我们构造出nxt数组之后,可以贪心的寻找子序列;

2.求一个序列的子序列个数;(可以限定序列的长度)

我们在DAG上跑拓扑DP,f[v][j]表示从1~v寻则j个元素的方案数;

显然的:f[v][j]+=f[u][j-1];

#include <bits/stdc++.h>
#define inc(i,a,b) for(register int i=a;i<=b;i++)
#define dec(i,a,b) for(register int i=a;i>=b;i--)
using namespace std;
char s[3010];
int nxt[3010][40];
int n,m;
long long f[3010][3010];
int rudu[3010];
const int p=998244353;
queue<int> qwq;
void tp()
{
	qwq.push(0);
	f[0][0]=1;
	while(qwq.size()){
		int u=qwq.front();
		qwq.pop();
		inc(i,0,25){
			if(!nxt[u][i]) continue;
			inc(j,0,u) f[nxt[u][i]][j+1]=(f[nxt[u][i]][j+1]+f[u][j])%p;
			--rudu[nxt[u][i]];
			if(rudu[nxt[u][i]]==0) qwq.push(nxt[u][i]);
		}
	}
}
int main()
{
	scanf("%s",s+1);
	n=strlen(s+1);cin>>m;
	dec(i,n-1,0){
		inc(j,0,25) nxt[i][j]=nxt[i+1][j];
		nxt[i][s[i+1]-‘a‘]=i+1;
		inc(j,0,25) if(nxt[i][j]!=0) rudu[nxt[i][j]]++;
	}
	tp();
    long long ans=0;
	inc(i,1,n) ans=(ans+f[i][m])%p;
	cout<<ans%p;
}
/*
addeade
3

aa
1
*/

3.求两串的公共子序列个数

两串都构造一下,直接跑就好了

long long dfs(int x,int y){
    if(f[x][y]) return f[x][y];
    for(int i=1;i<=26;++i)
        if(nxt1[x][i]&&nxt2[y][i])
            f[x][y]+=Dfs(nxt1[x][i],nxt2[y][i]);
    return ++f[x][y];
}

4.求字符串的回文子序列个数

首先原串与反串都建一遍;

就相当于从左右端点向中间跑自动机;

显然:x+y<=n+1才会合法;

但要注意,我们只能统计偶数长度的字符串,而不能统计奇数个数的字符串;

因为我们永远都是两个两个地串;

long long Dfs(int x,int y){
    if(f[x][y]) return f[x][y];
    for(int i=1;i<=a;++i)
        if(nxt1[x][i]&&nxt2[y][i]){
            if(nxt1[x][i]+nxt2[y][i]>n+1) continue;
            if(nxt1[x][i]+nxt2[y][i]<n+1) f[x][y]++;
            f[x][y]=(f[x][y]+Dfs(nxt1[x][i],nxt2[y][i]))%mod;
        }
    return ++f[x][y];
}

DAG

原文地址:https://www.cnblogs.com/kamimxr/p/11823052.html

时间: 2024-10-29 16:20:04

序列自动机 浅谈的相关文章

浅谈序列自动机

简介 序列自动机是一个可以快速判断字符串\(t\)是否是字符串\(s\)的子串的一个算法. 构造 对\(s\)构造序列自动机,使用\(Nxt_{i,j}\)代表从第\(i\)个位置开始,字符\(j\)出现的第一个位置.我们倒着遍历更新即可. int nxt[N][27]; void init(char *s){ int l=strlen(s); for(int i=0;i<26;i++) nxt[l][i]=INF; for(int i=l-1;i>=0;i--){ for(int j=0;j

浅谈算法和数据结构

: 一 栈和队列 http://www.cnblogs.com/yangecnu/p/Introduction-Stack-and-Queue.html 最近晚上在家里看Algorithems,4th Edition,我买的英文版,觉得这本书写的比较浅显易懂,而且“图码并茂”,趁着这次机会打算好好学习做做笔记,这样也会印象深刻,这也是写这一系列文章的原因.另外普林斯顿大学在Coursera 上也有这本书同步的公开课,还有另外一门算法分析课,这门课程的作者也是这本书的作者,两门课都挺不错的. 计算

浅谈IM软件业务知识——非对称加密,银行U盾的原理

概述 首先了解一下相关概念:RSA算法:1977年由Ron Rivest.Adi Shamirh和LenAdleman发明的,RSA就是取自他们三个人的名字.算法基于一个数论:将两个大素数相乘非常容易,但要对这个乘积的结果进行 因式分解却非常困难,因此可以把乘积公开作为公钥,该算法能够抵抗目前已知的所有密码攻击.RSA算法是一种非对称算法,算法需要一对密钥,使用其中一个 加密,需要使用另外一个才能解密.我们在进行RSA加密通讯时,就把公钥放在客户端,私钥留在服务器. RSA非对称加密算法,可以验

浅谈自然语言处理基础(下)

命名实体识别 命名实体的提出源自信息抽取问题,即从报章等非结构化文本中抽取关于公司活动和国防相关活动的结构化信息,而人名.地名.组织机构名.时间和数字表达式结构化信息的关键内容,所以需要从文本中去识别这些实体指称及其类别,即命名实体识别和分类. 21世纪以后,基于大规模语料库的统计方法成为自然语言处理的主流,以下是基于统计模型的命名实体识别方法归纳: 基于CRF的命名实体识别方法 基于CRF的命名实体识别方法简便易行,而且可以获得较好的性能,广泛地应用于人名.地名和组织机构等各种类型命名实体的识

浅谈UML的概念和模型之UML九种图

文件夹: UML的视图 UML的九种图 UML中类间的关系 上文我们介绍了,UML的视图,在每一种视图中都包括一个或多种图.本文我们重点解说UML每种图的细节问题: 1.用例图(use case diagrams) [概念]描写叙述用户需求,从用户的角度描写叙述系统的功能 [描写叙述方式]椭圆表示某个用例:人形符号表示角色 [目的]帮组开发团队以一种可视化的方式理解系统的功能需求 [用例图] 2.静态图 类图(class  diagrams) [概念]显示系统的静态结构,表示不同的实体是怎样相关

填坑——浅谈“离散时间信号的傅里叶变换”

之前学习计算机视觉,虽然敲了不少代码,但一直没弄懂傅里叶变换以及图像滤波背后的数学含义,只能对着现成的公式照葫芦画瓢,让我内心觉得深深的不安.好在通过这段时间在华为的实习,恶补了一下数字信号处理相关的基础知识,总算是把这方面的坑给填上了.以下为这几天的学习成果,也就是我自己对傅里叶变换的理解. 一.离散时间信号 要弄懂离散时间信号的傅里叶变换,首先要弄清楚什么是信号,而什么又是离散时间信号.(虽然感觉像是废话,可作为一个软件工程的童鞋我一开始对这些东西真的没概念啊T_T) 所谓信号,其实就是包含

浅谈算法和数据结构: 四 快速排序

原文:浅谈算法和数据结构: 四 快速排序 上篇文章介绍了时间复杂度为O(nlgn)的合并排序,本篇文章介绍时间复杂度同样为O(nlgn)但是排序速度比合并排序更快的快速排序(Quick Sort). 快速排序是20世纪科技领域的十大算法之一 ,他由C. A. R. Hoare于1960年提出的一种划分交换排序. 快速排序也是一种采用分治法解决问题的一个典型应用.在很多编程语言中,对数组,列表进行的非稳定排序在内部实现中都使用的是快速排序.而且快速排序在面试中经常会遇到. 本文首先介绍快速排序的思

浅谈希尔排序-----摘录

希尔排序 一.个人理解 希尔排序(Shell Sort)是插入排序的一种.是针对直接插入排序算法的改进.该方法又称缩小增量排序,因DL.Shell于1959年提出而得名. 其实,希尔排序本质也就是直接插入算法的升级,希尔的基本思想,就是先将整个待排元素序列分割成若干个子序列(由相隔某个"增量"的元素组成的)分别进行直接插入排序,然后依次缩减增量大小再进行排序,待整个序列中的元素基本有序(增量足够小,通常为1)时,再对全体元素进行一次直接插入排序. (直接插入的原理:把一个标记插入到已经

java序列化与反序列化以及浅谈一下hadoop的序列化

1.什么是序列化和反序列化 神马是序列化呢,序列化就是把内存中的对象的状态信息,转换成字节序列以便于存储(持久化)和网络传输.(网络传输和硬盘持久化,你没有一定的手段来进行辨别这些字节序列是什么东西,有什么信息,这些字节序列就是垃圾). 反序列化就是将收到字节序列或者是硬盘的持久化数据,转换成内存中的对象. 2.JDK的序列化 JDK的序列化只有实现了serializable接口就能实现序列化与反序列化,但是记得一定要加上序列化版本ID serialVersionUID 这个是识别序列化的之前那