文本生成器

首先考虑一个容斥,算出不包含任何一个单词的文章的数量。

我们设 \(dp_{i,j}\) 表示当前文章长度为 \(i\),最后一个字符在 \(AC\) 自动机上的 \(j\) 号点的方案数。我们要求的答案就是 \(\displaystyle 26^m-\sum_{i=0}^{idx}f_{m,i}\)。

于是我们考虑怎么转移。

首先,我们在建立 \(AC\) 自动机的时候,如果发现一个节点 \(i\) 指向的 \(ne_i\) 节点有结束标记,那么我们把这个点也打上结束标记。其他东西没有区别

然后我们对于 \(AC\) 自动机上的每一个点 \(j\),设他的其中一个字节点为 \(k\)。那么如果 \(k\) 节点没有结束标记,则 \(f_{i,k}+f_{i-1,j}\leftarrow f_{i,k}\)。于是就做完了,代码:

#include<bits/stdc++.h>
#define int long long
#define N 10005
#define M 105
#define mod 10007
using namespace std;
int tr[N][26],cnt[N],ne[N],n,m,idx,f[M][N];
char s[N];
//f_{i,j}表示字符串长度i,最后一个字符的点为j,且不包含任意一个模式串的方案数
int ksm(int x,int y){
int res=1;
while(y){
if(y&1)(res*=x)%=mod;
(x*=x)%=mod;
y>>=1;
}
return res;
}
void ins(){
int p=0;
for(int i=0;s[i];i++){
int t=s[i]-'A';
if(tr[p][t]==0)tr[p][t]=++idx;
p=tr[p][t];
}
cnt[p]=1;
}
void build(){
queue<int>q;
for(int i=0;i<26;i++){
if(tr[0][i]!=0){
q.push(tr[0][i]);
}
}
while(!q.empty()){
int t=q.front();
q.pop();
for(int i=0;i<26;i++){
int c=tr[t][i];
if(c==0){
tr[t][i]=tr[ne[t]][i];
}
else{
if(cnt[tr[ne[t]][i]]==1)cnt[tr[t][i]]=1;
ne[c]=tr[ne[t]][i];
q.push(c);
}
}
}
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>s;
ins();
}
build();
f[0][0]=1;//空字符串,方案数1
for(int i=1;i<=m;i++){
for(int j=0;j<=idx;j++){
for(int k=0;k<26;k++){
if(cnt[tr[j][k]]==1)continue;
(f[i][tr[j][k]]+=f[i-1][j])%=mod;
}
}
}
int sum=ksm(26,m),res=0;
for(int i=0;i<=idx;i++){
(res+=f[m][i])%=mod;
}
if(sum<res)sum+=mod;
cout<<sum-res;
return 0;
}

数数

发现就是上一题的 \(dp\) 变成了数位 \(dp\),于是我们采用记忆化搜索实现。

我们设 \(f_{i,j,k,l}\) 表示当前到了第 \(i\) 位数,最后一个数字在 \(AC\) 自动机上的位置为 \(j\),并且当前前 \(i\) 位是或不是和限制的数完全相同,当前前 \(i\) 位是不是全是 \(0\)。

但是由于我们记搜是最后到只剩一位才返回答案,相当于是倒着算的,所以我们初始要把限制数字翻转。

剩下的 \(dp\) 方式几乎和上一题一样,代码:

#include<bits/stdc++.h>
#define int long long
#define N 1605
#define mod 1000000007
using namespace std;
int tr[N][10],cnt[N],ne[N],n,m,idx,f[N][N][2][2];
string t;
char s[N];
void ins(string s){
int p=0;
for(int i=0;s[i];i++){
int t=s[i]-'0';
if(tr[p][t]==0)tr[p][t]=++idx;
p=tr[p][t];
}
cnt[p]=1;
}
void build(){
queue<int>q;
for(int i=0;i<10;i++){
if(tr[0][i]!=0){
q.push(tr[0][i]);
}
}
while(!q.empty()){
int t=q.front();
q.pop();
for(int i=0;i<10;i++){
int c=tr[t][i];
if(c==0){
tr[t][i]=tr[ne[t]][i];
}
else{
ne[c]=tr[ne[t]][i];
cnt[c]|=cnt[ne[c]];
q.push(c);
}
}
}
}
int dp(int dep,int ac_pos,bool is_lim,bool has_zer){
if(dep==0)return cnt[ac_pos]==0;
if(cnt[ac_pos]==1)return 0;
int &v=f[dep][ac_pos][is_lim][has_zer];
if(v!=-1)return v;
int lim=is_lim?(s[dep]-'0'):9ll;
int sum=0;
for(int i=0;i<=lim;i++){
int p1=(has_zer&&(i==0))?0:tr[ac_pos][i];
bool f1=(is_lim&&(i+'0'==s[dep]));
bool f2=(has_zer&&(i==0));
(sum+=dp(dep-1,p1,f1,f2))%=mod;
}
return v=sum;
}
signed main(){
cin>>s+1>>n;
int len=strlen(s+1);
reverse(s+1,s+len+1);
memset(f,-1,sizeof f);
for(int i=1;i<=n;i++){
cin>>t;
ins(t);
}
build();
int res=dp(len,0,1,1)+mod-1;
if(res>mod)res-=mod;
cout<<res;
return 0;
}

阿狸的打字机

首先我们要建出失配树。什么是失配树?就是把每个点连向他的失配指针构成的一棵树。例如:

接着我们考虑先建出 \(AC\) 自动机:

  • 小写字母,直接向下走。

  • P,存下这个点的编号。

  • B,回到父节点。

然后我们建出失配树。接着考虑怎么处理询问:

有多少个 \(x\) 在 \(y\) 中出现,事实上就是,有多少个 \(y\) 中的节点的 \(ne\) 指针直接或间接指向 \(x\) 的结束点。

于是我们对每个点 \(i\) 连一条 \(ne_i\rightarrow i\) 的边。如果 \(y\) 的一个点在 \(x\) 结束点为根的子树内,那么说明这个点的 \(ne\) 直接或间接指向 \(x\) 结束点。

所以我们把失配树做一个 \(dfs\) 序,使得一个点的子树变成一段区间,这就变成了一个单点修改,区间查询的问题。我们使用树状数组实现。

代码:

#include<bits/stdc++.h>
#define int long long
#define N 100005
using namespace std;
int n,m,tr[N][26],ne[N],idx;
int din[N],dout[N],res[N],sum,h[N];
int a[N],fa[N],tot;
string s;
struct edge{
int to,nxt;
}e[N];
struct ask{
int x,id;
};
vector<ask>q[N];
struct bit{
int c[N];
int lowbit(int x){
return x&-x;
}
void modify(int x,int v){
while(x<=sum){
c[x]+=v;
x+=lowbit(x);
}
}
int qry(int x){
int res=0;
while(x){
res+=c[x];
x-=lowbit(x);
}
return res;
}
}bits;
void add(int a,int b){
e[++tot]={b,h[a]};
h[a]=tot;
}
void ins(string s){
int p=0;
for(int i=0;s[i];i++){
if(s[i]>='a'&&s[i]<='z'){
int t=s[i]-'a';
if(tr[p][t]==0){
tr[p][t]=++idx;
fa[tr[p][t]]=p;
}
p=tr[p][t];
}
else if(s[i]=='P')a[++n]=p;
else p=fa[p];
}
}
void dfs(int u,int f){
din[u]=++sum;
for(int i=h[u];i;i=e[i].nxt){
int j=e[i].to;
if(j==f)continue;
dfs(j,u);
}
dout[u]=sum;
}
void build(){
queue<int>q;
for(int i=0;i<26;i++){
if(tr[0][i]!=0){
q.push(tr[0][i]);
}
}
while(!q.empty()){
int t=q.front();
q.pop();
for(int i=0;i<26;i++){
int c=tr[t][i];
if(c==0){
tr[t][i]=tr[ne[t]][i];
}
else{
ne[c]=tr[ne[t]][i];
q.push(c);
}
}
}
for(int i=1;i<=idx;i++){
add(ne[i],i);
}
dfs(0,0);
}
signed main(){
cin>>s>>m;
ins(s);
build();
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
q[y].push_back({x,i});
}
int len=s.size();
int id=0,p=0;
for(int i=0;i<len;i++){
if(s[i]>='a'&&s[i]<='z'){
int t=s[i]-'a';
p=tr[p][t];
bits.modify(din[p],1);
}
else if(s[i]=='P'){
id++;
for(auto j:q[id]){
int x=j.x,id=j.id;
res[id]=bits.qry(dout[a[x]])-bits.qry(din[a[x]]-1);
}
}
else{
bits.modify(din[p],-1);
p=fa[p];
}
}
for(int i=1;i<=m;i++){
cout<<res[i]<<'\n';
}
return 0;
}

AC自动机 提高篇的更多相关文章

  1. 【学术篇】SPOJ GEN Text Generator AC自动机+矩阵快速幂

    还有5天省选才开始点字符串这棵技能树是不是太晚了点... ~题目の传送门~ AC自动机不想讲了QAQ.其实很久以前是学过然后打过板子的, 但也仅限于打过板子了~ 之前莫名其妙学了一个指针版的但是好像不 ...

  2. 算法总结篇---AC自动机

    目录 写在前面 算法流程 引例: 概述: Trie树的构建(第一步) 失配指针(第二步) 构建失配指针 字典树和字典图 多模式匹配 例题 写在前面 鸣谢: OiWiki 「笔记」AC 自动机---Lu ...

  3. 多模字符串匹配算法之AC自动机—原理与实现

    简介: 本文是博主自身对AC自动机的原理的一些理解和看法,主要以举例的方式讲解,同时又配以相应的图片.代码实现部分也予以明确的注释,希望给大家不一样的感受.AC自动机主要用于多模式字符串的匹配,本质上 ...

  4. 浅谈AC自动机

    写在前面:从10月23日开始写这篇博文,离NOIP2018只有十多天了.坚持不停课的倔强蒟蒻(我)尽量每天挤时间多搞一搞信竞(然而还要准备期中考试).NOIP争取考一个好成绩吧. 一.简介 AC自动机 ...

  5. AC自动机学习笔记-2(Trie图&&last优化)

    我是连月更都做不到的蒟蒻博主QwQ 考虑到我太菜了,考完noip就要退役了,所以我决定还是把博客的倒数第二篇博客给写了,也算是填了一个坑吧.(最后一篇?当然是悲怆のnoip退役记啦QAQ) 所以我们今 ...

  6. AC自动机学习笔记-1(怎么造一台AC自动机?)

    月更博主又来送温暖啦QwQ 今天我们学习的算法是AC自动机.AC自动机是解决字符串多模匹配问题的利器,而且代码也十分好打=w= 在这一篇博客里,我将讲解AC自动机是什么,以及怎么构建一个最朴素的AC自 ...

  7. 初学AC自动机

    前言 一直听说\(AC\)自动机是一个很难很难的算法,而且它不在\(NOIP\)提高组范围内(这才是关键),所以我一直没去学. 最近被一些字符串题坑得太惨,于是下定决心去学\(AC\)自动机. 简介 ...

  8. AC 自动机刷题记录

    目录 简介 第一题 第二题 第三题 第四题 第五题 第六题 简介 这就是用来记录我对于<信息学奥赛一本通 · 提高篇>一书中的习题的刷题记录以及学习笔记. 一般分专题来写(全部写一起可能要 ...

  9. 浅析 AC 自动机

    目录 简述 AC 自动机是什么 AC 自动机有什么用 AC 自动机·初探 AC 自动机·原理分析 AC 自动机·代码实现 AC 自动机·更进一步 第一题 第二题 第三题 从 AC 自动机到 fail ...

  10. AC自动机-算法详解

    What's Aho-Corasick automaton? 一种多模式串匹配算法,该算法在1975年产生于贝尔实验室,是著名的多模式匹配算法之一. 简单的说,KMP用来在一篇文章中匹配一个模式串:但 ...

随机推荐

  1. MySQL Explain 关键字详解

    概述 explain 关键字可以模拟执行 sql 查询语句,输出执行计划,分析查询语句的执行性能 使用方式如下:explain + sql explain select * from t1 执行计划各 ...

  2. python正则表达式替换所有内容并同时保留找到的内容

    除了一些专业的工具,例如ue,大部分编程语言的函数包都挺让人迷惑的,例如Java,js. 因为的确有许多功能是很常用的,但是他们又不提供,非得要程序员自己去实现,或者是利用三方的包. 到底是什么理由了 ...

  3. 高通UEFI中的I2C的方式读取TP的id

    高通UEFI中的I2C的方式读取TP的id 原文:https://blog.csdn.net/mengluoxixiang/article/details/100103347 老规矩,先说要实现的功能 ...

  4. 专用M4F+四核A53,异构多核AM62x让工业控制“更实时、更安全” Tronlong创龙科技5 秒前 1 德州仪器 TI芯片

    Cortex-M4F + Cortex-A53异构多核给工业控制带来何种意义? 创龙科技SOM-TL62x工业核心板搭载TI AM62x最新处理器,因其Cortex-M4F + Cortex-A53异 ...

  5. TI AM64x开发板规格书(双核ARM Cortex-A53 + 单/四核Cortex-R5F + 单核Cortex-M4F,主频1GHz)

    1 评估板简介 创龙科技TL64x-EVM是一款基于TI Sitara系列AM64x双核ARM Cortex-A53 + 单/四核Cortex-R5F + 单核Cortex-M4F多核处理器设计的高性 ...

  6. Java反射机制原理详解

    什么是反射? Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法.本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息 ...

  7. Java 面向对象编程之接口

    什么是接口? 是抽象方法的集合,接口通常以interface来声明,一个类通过继承接口的方式,从而来继承接口的抽象方法 语法 interface 名称 [extends 其他的接⼝名] { // 声明 ...

  8. Vue 3 后端错误消息处理范例

    1. 错误消息格式 前后端消息传递时,我们可以通过 json 的 errors 字段传递错误信息,一个比较好的格式范例为: { errors: { global: ["网络错误"] ...

  9. 吐血整理如何在Google Earth Engine上写循环 五个代码实例详细拆解

    在这里同步一篇本人的原创文章.原文发布于2023年发布在知乎专栏,转移过来时略有修改.全文共计3万余字,希望帮助到GEE小白快速进阶. 引言 这篇文章主要解答GEE中.map()和.iterate() ...

  10. Python 插件式程序设计与开发实践总结

    插件式程序设计与开发实践总结 By:授客 QQ:1033553122 开发环境 win 10 python 3.6.5 代码结构