【题解】AC自动机题解合集
最近貌似大家都在搞字符串?很长一段时间都没有写博客了……还是补一补坑吧。
感觉AC自动机真的非常优美了,通过在trie树上建立fail指针可以轻松解决多模匹配的问题。实际上在AC自动机上的匹配可以看做是拿着一个串在上面跑,在固定一个左端点的时候尽量地向右匹配。如果发现实在是匹配不下去了,就向右挪动左端点实现新的匹配(跳转fail指针)。基本上根据这一条理解,就可以解决大部分的问题了。
AC自动机裸考的不多,除了匹配之外一个较常见的搭配就是和DP结合在一起。但本质上依然是在匹配串,只要根据fail指针的指向去转移dp状态即可。
1.[HNOI2006] 最短母串问题
非常明确的指向:n <= 12。一眼状压,我们建立状态 \(f[u][S]\) 表示在匹配到AC自动机上的状态 \(u\) 的时候,已经匹配上的串为 \(S\) 集合时的方案数。也许会有疑问:那么怎么保证步数最短&能够输出字典序最小的解?注意AC自动机上相邻状态的转移意味着添加了一个字符,这样我们可以方便地BFS转移。优先转移小的字符可以保证字典序最小,发现答案后直接输出即可。
#include <bits/stdc++.h>
using namespace std;
#define maxn 605
#define maxc 55
#define maxm 5000
int n, tot, cnt, Ans[maxn];
int ch[maxn][], fail[maxn];
int mark[maxn], bits[];
char s[maxc]; struct node
{
int b;
short a, c;
node(short _a = , int _b = , short _c = -)
{ a = _a, b = _b, c = _c; }
}g[maxn][maxm], ans; queue <node> q; void Ins(int x)
{
int L = strlen(s + ), p = ;
for(int i = ; i <= L; i ++)
{
int u = s[i] - 'A';
if(!ch[p][u]) ch[p][u] = ++ tot;
p = ch[p][u];
}
mark[p] = (mark[p] | bits[x - ]);
} void Build()
{
queue <int> q;
for(int i = ; i < ; i ++)
if(ch[][i]) q.push(ch[][i]);
while(!q.empty())
{
int u = q.front(); q.pop();
for(int i = ; i < ; i ++)
{
if(ch[u][i])
{
fail[ch[u][i]] = ch[fail[u]][i];
mark[ch[u][i]] |= mark[fail[ch[u][i]]];
q.push(ch[u][i]);
}
else ch[u][i] = ch[fail[u]][i];
}
}
} void DP()
{
q.push(node(, )); g[][].c = -;
while(!q.empty())
{
node now = q.front(); q.pop();
int u = now.a, S = now.b;
if(S == bits[n] - ) { ans = node(u, S, g[u][S].c); break; }
for(int i = ; i < ; i ++)
{
int v = ch[u][i], s = S | mark[v];
if(g[v][s].c == -)
{
g[v][s] = node(u, S, i);
q.push(node(v, s));
}
}
}
} int main()
{
scanf("%d", &n);
bits[] = ; for(int i = ; i < ; i ++) bits[i] = bits[i - ] << ;
for(int i = ; i <= n; i ++)
{
scanf("%s", s + );
Ins(i);
}
Build(); DP();
for(; g[ans.a][ans.b].c != -; ans = g[ans.a][ans.b])
Ans[++ cnt] = g[ans.a][ans.b].c;
for(int i = cnt; i >= ; i --) printf("%c", Ans[i] + 'A');
return ;
}
2.[JSOI2009] 密码
emmmm……如果没有输出方案一说,和上题完全就是一样的做法但是我们要输出方案呀?想想如果想要在AC自动机上去爆搜也保证复杂度的话,大概借助一个dp数组表示从当前状态往后转移是否可能出现合法解就好了吧?所以状态的设立定为从当前状态走到目的状态的方案数。记忆化搜索大法好!(但是好像没有人这么写?明明这样写真的又无脑又简单呀……)
#include <bits/stdc++.h>
using namespace std;
#define maxn 100000
#define int long long
int n, m, tot, bits[], f[][][];
int cnt, mark[maxn], fail[maxn], ch[maxn][];
char s[maxn]; int read()
{
int x = , k = ;
char c; c = getchar();
while(c < '' || c > '') { if(c == '-') k = -; c = getchar(); }
while(c >= '' && c <= '') x = x * + c - '', c = getchar();
return x * k;
} void Ins(int x)
{
int t = strlen(s + ), p = ;
for(int i = ; i <= t; i ++)
{
int u = s[i] - 'a';
if(!ch[p][u]) ch[p][u] = ++ tot;
p = ch[p][u];
}
mark[p] |= bits[x - ];
} void Build()
{
queue <int> q;
for(int i = ; i < ; i ++)
if(ch[][i]) q.push(ch[][i]);
while(!q.empty())
{
int u = q.front(); q.pop();
for(int i = ; i < ; i ++)
{
int v = ch[u][i];
if(v)
{
fail[v] = ch[fail[u]][i];
mark[v] |= mark[fail[v]];
q.push(v);
}
else ch[u][i] = ch[fail[u]][i];
}
}
} void Up(int &x, int y) { x = (x + y); }
int DP(int x, int y, int z)
{
if(z == n && y == bits[m] - ) return f[x][y][z] = ;
else if(z == n) return ;
if(f[x][y][z] != -) return f[x][y][z];
else f[x][y][z] = ;
for(int c = ; c < ; c ++)
{
int v = ch[x][c];
Up(f[x][y][z], DP(v, y | mark[v], z + ));
}
return f[x][y][z];
} void dfs(int x, int y, int z)
{
if(z == n)
{
for(int i = ; i <= cnt; i ++) printf("%c", s[i]);
puts(""); return;
}
for(int c = ; c < ; c ++)
{
int v = ch[x][c];
if(f[v][y | mark[v]][z + ] > )
{
s[++ cnt] = c + 'a';
dfs(v, y | mark[v], z + );
cnt --;
}
}
} signed main()
{
n = read(), m = read();
memset(f, -, sizeof(f));
bits[] = ; for(int i = ; i < ; i ++) bits[i] = bits[i - ] << ;
for(int i = ; i <= m; i ++)
{
scanf("%s", s + );
Ins(i);
}
Build(); DP(, , );
printf("%lld\n", f[][][]);
if(f[][][] > ) return ;
dfs(, , );
return ;
}
3.[BJOI2017] 魔法咒语
这题首先观察一下数据范围,发现一定是两种做法的题(并没有统一的数据范围)。前面的直接暴力建立状态 \(f[i][j]\) 表示第 \(i\) 个字符匹配到了AC自动机上的 \(j\) 状态的方案数。可以枚举用哪一个串转移,只要不会踩到禁忌状态就可以转移。为了降低复杂度,可以预处理一下。至于后面的数据,看到这么大的数据范围显然矩阵。发现长度 <= 2;所以我们可以有:
差不多这样子去构造矩阵。状态和转移方式是不变的,构造矩阵优化dp就好。
#include <bits/stdc++.h>
using namespace std;
#define maxn 6300
#define maxm 205
#define mod 1000000007
int n, m1, m2, ans, tot, f[maxm][maxn];
int len[maxn], ch[maxn][], fail[maxn];
int rec1[maxn][maxn], rec2[maxn][maxn], trans[maxn][maxm];
bool error[maxn];
char s[maxm][maxm]; struct matrix
{
int a[][];
matrix() { memset(a, , sizeof(a)); }
friend matrix operator *(const matrix& a, const matrix& b)
{
matrix c;
memset(c.a, , sizeof(c.a));
int t = tot * ;
for(int i = ; i <= t; i ++)
for(int j = ; j <= t; j ++)
for(int k = ; k <= t; k ++)
c.a[i][j] = (c.a[i][j] + 1ll * a.a[i][k] * b.a[k][j] % mod) % mod;
return c;
}
}M; void Up(int &x, int y) { x = (x + y); if(x >= mod) x -= mod; }
void Ins(int x)
{
int p = ; len[x] = strlen(s[x] + );
for(int i = ; i <= len[x]; i ++)
{
int v = s[x][i] - 'a';
if(!ch[p][v]) ch[p][v] = ++ tot;
p = ch[p][v];
}
error[p] = ;
} int Get(int u, int x)
{
int p = u;
for(int i = ; i <= len[x]; i ++)
{
int v = s[x][i] - 'a';
if(error[p]) return -;
p = ch[p][v];
}
if(error[p]) return -;
return p;
} void Build()
{
queue <int> q;
for(int i = ; i < ; i ++) if(ch[][i]) q.push(ch[][i]);
while(!q.empty())
{
int u = q.front(); q.pop();
for(int i = ; i < ; i ++)
{
int v = ch[u][i];
if(v)
{
fail[v] = ch[fail[u]][i];
error[v] |= error[fail[v]];
q.push(v);
}
else ch[u][i] = ch[fail[u]][i];
}
}
for(int j = ; j <= tot; j ++)
for(int i = ; i <= m1; i ++)
{
trans[j][i] = Get(j, i); if(trans[j][i] == -) continue;
if(len[i] == ) rec1[j + ][trans[j][i] + ] ++;
else rec2[j + ][trans[j][i] + ] ++;
}
} void DP1()
{
f[][] = ;
for(int k = ; k <= n; k ++)
for(int i = ; i <= tot; i ++)
{
if(!f[k][i]) continue;
for(int j = ; j <= m1; j ++)
{
int t = trans[i][j];
if(t == -) continue;
else if(k + len[j] <= n) Up(f[k + len[j]][t], f[k][i]);
}
}
for(int i = ; i <= tot; i ++)
if(!error[i]) Up(ans, f[n][i]);
} matrix Qpow(int timer)
{
matrix base;
memset(base.a, , sizeof(base.a));
for(int i = ; i <= * tot; i ++) base.a[i][i] = ;
for(; timer; timer >>= , M = M * M)
if(timer & ) base = base * M;
return base;
} void DP2()
{
tot ++; int t = * tot;
for(int i = tot + ; i <= t; i ++) M.a[i][i - tot] = ;
for(int i = ; i <= tot; i ++)
for(int j = tot + ; j <= t; j ++)
M.a[i][j] = rec2[i][j - tot];
for(int i = tot + ; i <= t; i ++)
for(int j = tot + ; j <= t; j ++)
M.a[i][j] = rec1[i - tot][j - tot]; matrix ret = Qpow(n), S;
memset(S.a, , sizeof(S.a)); S.a[][tot + ] = ;
S = S * ret;
for(int i = tot + ; i <= t; i ++)
if(!error[i - tot - ]) Up(ans, S.a[][i]);
} signed main()
{
scanf("%d%d%d", &m1, &m2, &n);
for(int i = ; i <= m1; i ++) scanf("%s", s[i] + ), len[i] = strlen(s[i] + );
for(int i = ; i <= m2; i ++) scanf("%s", s[m1 + ] + ), Ins(m1 + );
Build();
if(n <= ) DP1(); else DP2();
printf("%d\n", ans);
return ;
}
【题解】AC自动机题解合集的更多相关文章
- [题解+总结]动态规划大合集II
1.前言 大合集总共14道题,出自江哥之手(这就没什么好戏了),做得让人花枝乱颤.虽说大部分是NOIP难度,也有简单的几道题目,但是还是做的很辛苦,有几道题几乎没思路,下面一道道边看边分析一下. 2. ...
- [JSOI2012]玄武密码 题解(AC自动机)
显然是AC自动机对吧 插入单词之后把文章在自动机上跑一遍,到达过的节点打上花火标记 之后检查一下每个单词有几个标记即可 可以把题目中的4个字母映射成abcd方便遍历 一定要记得把文章也映射啊! #in ...
- 【bzoj3881】[Coci2015]Divljak AC自动机+树链的并+DFS序+树状数组
题目描述 Alice有n个字符串S_1,S_2...S_n,Bob有一个字符串集合T,一开始集合是空的. 接下来会发生q个操作,操作有两种形式: “1 P”,Bob往自己的集合里添加了一个字符串P. ...
- 【bzoj1030】[JSOI2007]文本生成器 AC自动机+dp
题目描述 JSOI交给队员ZYX一个任务,编制一个称之为“文本生成器”的电脑软件:该软件的使用者是一些低幼人群,他们现在使用的是GW文本生成器v6版.该软件可以随机生成一些文章―――总是生成一篇长度固 ...
- 【bzoj2938】[Poi2000]病毒 AC自动机
题目描述 二进制病毒审查委员会最近发现了如下的规律:某些确定的二进制串是病毒的代码.如果某段代码中不存在任何一段病毒代码,那么我们就称这段代码是安全的.现在委员会已经找出了所有的病毒代码段,试问,是否 ...
- 【bzoj3940】[Usaco2015 Feb]Censoring AC自动机
题目描述 Farmer John has purchased a subscription to Good Hooveskeeping magazine for his cows, so they h ...
- 【bzoj3530】[Sdoi2014]数数 AC自动机+数位dp
题目描述 我们称一个正整数N是幸运数,当且仅当它的十进制表示中不包含数字串集合S中任意一个元素作为其子串.例如当S=(22,333,0233)时,233是幸运数,2333.20233.3223不是幸运 ...
- 【bzoj1195】[HNOI2006]最短母串 AC自动机+状态压缩+BFS最短路
原文地址:http://www.cnblogs.com/GXZlegend/p/6825226.html 题目描述 给定n个字符串(S1,S2,„,Sn),要求找到一个最短的字符串T,使得这n个字符串 ...
- 【bzoj1444】[Jsoi2009]有趣的游戏 AC自动机+矩阵乘法
题目描述 输入 注意 是0<=P 输出 样例输入 样例输出 题解 AC自动机+矩阵乘法 先将所有字符串放到AC自动机中,求出Trie图. 然后构建邻接矩阵:如果x不是某个字符串的末位置,则x连向 ...
随机推荐
- Swing 解决 idea 找不到创建gui form的问题
果然,寄希望于百度google不如自己动手,还是得吃透文档, 然后就是对于别人的博客要严格对照步骤来,否则都容易达不到效果 这边gui form在idea下找不到创建,百度google一个说的也没有, ...
- Jquery操作下拉列表和复选框,自定义下拉
后半部分还有自定义下拉列表和开灯关灯的效果,可以进来来看一下 哦 如果网页有下拉列表和复选框,看一下Jquery怎么来操作他们,主要怎么来选取他们的数据,怎么设置某一项选中 先来看个下拉列表 < ...
- linux ——使用find如何快速替换所有相同参数
在生成环境上有时候需要大规模修改某一配置里的参数,但是该参数存在多个地方,比如IP地址 端口 项目名等,特别是项目名称混乱想统一 find /项目地址 -type f |xargs grep &qu ...
- Sublime Text 2 - Unable to find git.exe 错误
今日打开 Sublime Text 2,随即弹出 Package Control - Unable to find git.exe 错误.如下, 原因:曾经通过 git clone 命令获取过 Sub ...
- 使用 Sublime Text 做 Javascript 编辑器 - 集成 JSHint 问题检测工具
JSHint(jshint.com)是 Javascritp 代码质量工具,可以帮助开发人员发现 Javascript 代码中的错误和潜在的问题.jshint.com 是一个在线编辑器,我们可以为 S ...
- zend安装及破解
Zend下载 https://pan.baidu.com/s/1fCfUQ0j7dxEtOzbNnsgODg 破解: 1.打开dmg文件安装,将Zend Studio拖拽至applications进行 ...
- Java变量声明,实例化,问题
1.变量在输出前必须实例化,这是因为只有声明,没有分配内存空间 在这种情况下会报错 2.实例化后,尽管没有赋值,可能是默认了吧,但也不会输出null,什么也没有输出 上面的理解可能是错的,a赋值了,就 ...
- 2018软工实践—Alpha冲刺(4)
队名 火箭少男100 组长博客 林燊大哥 作业博客 Alpha 冲鸭鸭鸭鸭! 成员冲刺阶段情况 林燊(组长) 过去两天完成了哪些任务 协调各成员之间的工作 协助前后端接口的开发 测试项目运行的服务器环 ...
- 线段树-hdu2795 Billboard(贴海报)
hdu2795 Billboard 题意:h*w的木板,放进一些1*L的物品,求每次放空间能容纳且最上边的位子 思路:每次找到最大值的位子,然后减去L 线段树功能:query:区间求最大值的位子(直接 ...
- 第九章 Mysql函数
简介 数学函数:处理数字 字符串函数:处理字符串 日期和时间函数:处理日期和时间,获取时间 条件判断函数:控制条件选择 系统信息函数:获取MySQL系统信息,包括数据库名称,当前用户名和数据库版本 加 ...