AC自动机 提高篇
文本生成器
首先考虑一个容斥,算出不包含任何一个单词的文章的数量。
我们设 \(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自动机 提高篇的更多相关文章
- 【学术篇】SPOJ GEN Text Generator AC自动机+矩阵快速幂
		
还有5天省选才开始点字符串这棵技能树是不是太晚了点... ~题目の传送门~ AC自动机不想讲了QAQ.其实很久以前是学过然后打过板子的, 但也仅限于打过板子了~ 之前莫名其妙学了一个指针版的但是好像不 ...
 - 算法总结篇---AC自动机
		
目录 写在前面 算法流程 引例: 概述: Trie树的构建(第一步) 失配指针(第二步) 构建失配指针 字典树和字典图 多模式匹配 例题 写在前面 鸣谢: OiWiki 「笔记」AC 自动机---Lu ...
 - 多模字符串匹配算法之AC自动机—原理与实现
		
简介: 本文是博主自身对AC自动机的原理的一些理解和看法,主要以举例的方式讲解,同时又配以相应的图片.代码实现部分也予以明确的注释,希望给大家不一样的感受.AC自动机主要用于多模式字符串的匹配,本质上 ...
 - 浅谈AC自动机
		
写在前面:从10月23日开始写这篇博文,离NOIP2018只有十多天了.坚持不停课的倔强蒟蒻(我)尽量每天挤时间多搞一搞信竞(然而还要准备期中考试).NOIP争取考一个好成绩吧. 一.简介 AC自动机 ...
 - AC自动机学习笔记-2(Trie图&&last优化)
		
我是连月更都做不到的蒟蒻博主QwQ 考虑到我太菜了,考完noip就要退役了,所以我决定还是把博客的倒数第二篇博客给写了,也算是填了一个坑吧.(最后一篇?当然是悲怆のnoip退役记啦QAQ) 所以我们今 ...
 - AC自动机学习笔记-1(怎么造一台AC自动机?)
		
月更博主又来送温暖啦QwQ 今天我们学习的算法是AC自动机.AC自动机是解决字符串多模匹配问题的利器,而且代码也十分好打=w= 在这一篇博客里,我将讲解AC自动机是什么,以及怎么构建一个最朴素的AC自 ...
 - 初学AC自动机
		
前言 一直听说\(AC\)自动机是一个很难很难的算法,而且它不在\(NOIP\)提高组范围内(这才是关键),所以我一直没去学. 最近被一些字符串题坑得太惨,于是下定决心去学\(AC\)自动机. 简介 ...
 - AC 自动机刷题记录
		
目录 简介 第一题 第二题 第三题 第四题 第五题 第六题 简介 这就是用来记录我对于<信息学奥赛一本通 · 提高篇>一书中的习题的刷题记录以及学习笔记. 一般分专题来写(全部写一起可能要 ...
 - 浅析 AC 自动机
		
目录 简述 AC 自动机是什么 AC 自动机有什么用 AC 自动机·初探 AC 自动机·原理分析 AC 自动机·代码实现 AC 自动机·更进一步 第一题 第二题 第三题 从 AC 自动机到 fail ...
 - AC自动机-算法详解
		
What's Aho-Corasick automaton? 一种多模式串匹配算法,该算法在1975年产生于贝尔实验室,是著名的多模式匹配算法之一. 简单的说,KMP用来在一篇文章中匹配一个模式串:但 ...
 
随机推荐
- cdh版本 livy部署
			
1.livy部署主要就是依赖spark_home的环境变量 如何找到spark_home在哪 locate spark-shell locate是个linux找文件的命令,直接找到该目录
 - 记录一次MySQL多表查询,order by不走索引的情况.
			
首先是表结构,部分字段脱敏已删除 CREATE TABLE `log_device_heart` ( `id` int unsigned NOT NULL AUTO_INCREMENT, `devic ...
 - WPF/C#:在DataGrid中显示选择框
			
前言 在使用WPF的过程中可能会经常遇到在DataGrid的最前或者最后添加一列选择框的需求,今天跟大家分享一下,在自己的项目中是如何实现的. 整体实现效果如下: 如果对此感兴趣,可以接下来看具体实现 ...
 - 上交大开源镜像站下架 Docker Hub 镜像
			
 在现代软件开发中,Docker镜像已经成为不可或缺的工具.然而,最近频频出现的Docker镜像下架事件让许多开发者措手不及.突然失去依赖的镜像,不仅打乱了项目进程,还引发了许多不便.那么,面对Do ...
 - 使用getevent在Android中调试输入子系统
			
# Android getevent用法详解 背景 在调试安卓设备按键,想使用hexdump,但是发现没有找到,反而找到了这个更好用的工具. 以下是我的调试片段 # getevent -l /dev/ ...
 - 复习 - es6语法
			
这几天电脑有点问题,一直在弄,而且论文也逼近了也在时间弄那个 ,前面node有一个大项目,已经做完了,我现在是准备把上次复习断下的继续复习一直到这个项目,然后就开始vue了. 1. 首先是函数的一个进 ...
 - hive、hbase、clickhouse
			
hive相当于贝利,是计算处理数据的鼻祖,hbase相当于梅西,继承了hive(贝利)的意志,但是因为现代足球的发展,梅西整体水平要强于贝利的远古踢法(mapreduce),然后clickhouse相 ...
 - LVGL一键打包图片工具,全部图片打包成一个bin文件,支持nor flash XIP模式下直接访问数据显示
			
最近做工程项目,需要用到LVGL,但是搜了很长时间没有看到合适的图片打包工具,大多都是生成数组或者单个的bin文件,这样烧录到nor flash很麻烦 后来看到一篇博客,博主的想法与我类似,不过他后面 ...
 - 松灵机器人scout mini小车 自主导航(2)——仿真指南
			
松灵机器人Scout mini小车仿真指南 之前介绍了如何通过CAN TO USB串口实现用键盘控制小车移动.但是一直用小车测试缺乏安全性.而松灵官方贴心的为我们准备了gazebo仿真环境,提供了完整 ...
 - Django查询特定条件的数据并插入其他表格模型
			
要将特定 wk_nu 值对应的数据批量插入到 MPS005D3Model 中,你可以执行以下步骤: 确定要插入的 wk_nu 值. 获取与该 wk_nu 相关的数据. 将获取的数据逐一创建为 MPS0 ...