【ACWing】1052. 设计密码
题目地址:
https://www.acwing.com/problem/content/1054/
你现在需要设计一个密码SSS,SSS需要满足:SSS的长度是NNN;SSS只包含小写英文字母;SSS不包含子串TTT;例如:abcabcabc和abcdeabcdeabcde是abcdeabcdeabcde的子串,abdabdabd不是abcdeabcdeabcde的子串。请问共有多少种不同的密码满足要求?由于答案会非常大,请输出答案模109+710^9+7109+7的余数。
输入格式:
第一行输入整数NNN,表示密码的长度。第二行输入字符串TTT,TTT中只包含小写字母。
输出格式:
输出一个正整数,表示总方案数模109+710^9+7109+7后的结果。
数据范围:
1≤N≤501≤N≤501≤N≤50
1≤∣T∣≤N1≤|T|≤N1≤∣T∣≤N,∣T∣|T|∣T∣是TTT的长度。
字符串匹配的过程可以用状态机来模拟。实际上KMP算法就是在模拟一种状态机,该状态机是依据模式串生成,并且可以接受以模式串为子串的任意字符串。即,我们先由模式串生成KMP里的next数组,接着用这个数组构造出一个有限状态自动机,使得这个DFA(Deterministic Finite Automaton,确定有限状态自动机的英文缩写)只接受以该模式串为子串的字符串。我们以字符串"aba"
为模式串举例,具体来说如下:
1、先求出s = "aba"
的next数组。参考https://blog.csdn.net/qq_46105170/article/details/113805346。这里的next数组可以是未优化版的也可以是优化版的,因为本质上来说,它们构造出的DFA识别的语言是一样的。
2、构造一个DFA,它有三个状态,分别是0,1,2,30,1,2,30,1,2,3,000是初始状态,333是接受状态(该DFA只有一个接受状态)。先构造边δ(0,a)=1,δ(1,b)=2,δ(2,a)=3\delta(0,a)=1,\delta(1,b)=2,\delta(2,a)=3δ(0,a)=1,δ(1,b)=2,δ(2,a)=3,这三个转移边是显然的,对应的情况是恰好存在子串"aba"
。接下来考虑失配边,这个字符串的next数组是ne=[−1,0,0]n_e=[-1,0,0]ne=[−1,0,0],这个数组规定了如果在s[i]s[i]s[i]存在失配,应该如何跳转。跳转规则如下:比如在s[1]s[1]s[1]和s[2]s[2]s[2]处失配的时候(对应的是当前状态在111和222的时候,比如当前位于状态111,然后读入了'a'
字符,这样就发生了失配),比如当前读到的字符是'c'
,那么就回到状态000,然后继续在状态000处匹配'c'
(也就是看一下s[0]s[0]s[0]是否等于'c'
),仍然不匹配,那么就跳到ne[0]=−1n_e[0]=-1ne[0]=−1,这个−1-1−1并不是一个真实状态,它是一个虚拟的状态,假想s[−1]s[-1]s[−1]是一个通配符,可以匹配任意字符,那么此时就匹配上了,沿着这条虚拟的边走到状态000(所以我们可以特判一下状态−1-1−1就行了,不需要真在程序里写这个状态);再比如,在状态111的时候读到了字符'b'
,那么此时是匹配的,直接沿着匹配边走到下一个状态,也就是状态222。
3、总结一下该DFA的跳转规则。当当前位于状态iii,并且读入了字符α\alphaα的时候,进行如下循环:只要当前处于的状态jjj不是−1-1−1,并且s[j]s[j]s[j]和α\alphaα不匹配,那么就跳转到ne[j]n_e[j]ne[j]去,直到j=−1j=-1j=−1或者s[j]=αs[j]=\alphas[j]=α为止,此时沿着匹配边走一步到状态j+1j+1j+1去。总结来说就是δ(i,α)=j+1\delta(i,\alpha)=j+1δ(i,α)=j+1。
由于上述DFA接受某个字符串,当且仅当其以构造该DFA的模式串为子串。这样一来,要求不含SSS为子串的字符串数量,相当于就是在问,从DFA的状态000出发,跳转NNN次,并且中途和终点没有跳到接受状态lSl_SlS的路径个数。这可以用动态规划来做。设f[k][p]f[k][p]f[k][p]是跳kkk步跳到状态ppp的路径条数,那么可以按照跳到状态ppp之前在哪儿来分类,则有:f[k][p]=∑q→pf[k−1][q]f[k][p]=\sum_{q\to p} f[k-1][q]f[k][p]=q→p∑f[k−1][q]初始条件f[0][0]=1f[0][0]=1f[0][0]=1(因为初始状态就是状态000)。最终答案就是:∑p=0lS−1f[N][p]\sum_{p=0}^{l_S-1} f[N][p]p=0∑lS−1f[N][p]即跳NNN步没有跳到状态lSl_SlS的路径条数。
由于不方便知道某个状态之前是哪个状态,我们可以用当前状态来更新未来状态,即可以用f[k−1][q]f[k-1][q]f[k−1][q]来累加到f[k][p]f[k][p]f[k][p]上去。代码如下:
#include <iostream>
#include <cstring>
using namespace std;const int N = 55, mod = 1e9 + 7;
int n, m;
char s[N];
int f[N][N];
int ne[N];// 求next数组
void build_ne() {ne[0] = -1;for (int i = 0, j = -1; i < m - 1;)if (j < 0 || s[j] == s[i]) {i++;j++;ne[i] = s[i] != s[j] ? j : ne[j];} else j = ne[j];
}int main() {scanf("%d%s", &n, s);m = strlen(s);build_ne();f[0][0] = 1;// 枚举步数for (int i = 1; i <= n; i++)// 枚举当前在哪个状态for (int j = 0; j < m; j++)// 枚举当前在状态j的时候获得的输入字符for (char ch = 'a'; ch <= 'z'; ch++) {// 开始计算从状态j开始,读入ch后会跳到哪个状态int u = j;while (u != -1 && ch != s[u]) u = ne[u];u++;// 状态s.size()是接受态,走到其的路径条数不用计算,否则累加一下路径条数if (u < m) f[i][u] = (f[i][u] + f[i - 1][j]) % mod;}int res = 0;for (int i = 0; i < m; i++) res = (res + f[n][i]) % mod;printf("%d\n", res);
}
时间复杂度O(NlS2)O(Nl_S^2)O(NlS2),空间O(NlS)O(Nl_S)O(NlS)。
下面给出字符串下标从111开始的版本。此版本中,设字符串长度mmm,那么状态是0,1,...,m0,1,...,m0,1,...,m这些,其中mmm是匹配状态,本题中不考虑。每个状态uuu在读入字符串ccc的时候,当c≠s[u+1]c\ne s[u+1]c=s[u+1]的时候发生跳转,一直跳转到nek[u]ne^k[u]nek[u]直到u=0∨c=s[u+1]u=0\lor c= s[u+1]u=0∨c=s[u+1]成立,如果c=s[u+1]c=s[u+1]c=s[u+1]则uuu向后跳一格。代码如下:
#include <iostream>
#include <cstring>
using namespace std;const int N = 55, mod = 1e9 + 7;
int n, m;
char s[N];
int f[N][N], ne[N];void build_ne() {for (int i = 2, j = 0; i <= m; i++) {while (j && s[i] != s[j + 1]) j = ne[j];if (s[i] == s[j + 1]) j++;ne[i] = i < m && s[i + 1] != s[j + 1] ? j : ne[j];}
}int main() {cin >> n >> s + 1;m = strlen(s + 1);build_ne();f[0][0] = 1;for (int i = 0; i < n; i++)for (int j = 0; j < m; j++)for (char ch = 'a'; ch <= 'z'; ch++) {int u = j;while (u && s[u + 1] != ch) u = ne[u];if (s[u + 1] == ch) u++;if (u < m) f[i + 1][u] = (f[i + 1][u] + f[i][j]) % mod;}int res = 0;for (int j = 0; j < m; j++) res = (res + f[n][j]) % mod;printf("%d\n", res);
}
时空复杂度一样。
【ACWing】1052. 设计密码相关推荐
- 1052. 设计密码
你现在需要设计一个密码 S,S 需要满足: S 的长度是 N: S 只包含小写英文字母: S 不包含子串 T: 例如:abc 和 abcde 是 abcde 的子串,abd 不是 abcde 的子串. ...
- 解锁秋天\秋季借势的海波设计密码!
属于夏季的节气在热潮中离我们远去,一朝秋暮露成霜,荷败千池.白棉万顷.秋菊凌霜.芙蓉独芳.要说一种节气能让大自然都心生敬畏,那便是秋收冬藏的霜降时分.午后阳光不再灼热,于寒冷秋日,更显温暖.品尝一块满 ...
- 免费UI圆角字体素材|字体设计密码:字形设计中“圆角”的应用规范
什么是圆角呢? 是指在字形制作过程为了给字形添加细节而用到的一个方法,在笔画相交或转折处.一般有内.外圆角两种. 外圆角 内圆角 给字形添加圆角虽然可以增加字形的精致感,但也不能随意添加,流于俗套不说 ...
- DP 状态机模型 AcWing算法提高课 详解
状态机模型 AcWing 1049. 大盗阿福 #include <iostream> #include <algorithm> #include <cmath> ...
- acwing提高组 第一章 动态规划
文章目录 数字三角形模型 最长上升子序列模型 背包模型 状态机模型 状态压缩DP 区间DP 树形DP 数位DP 单调队列优化DP 斜率优化DP oj链接 数字三角形模型 AcWing 1015. 摘花 ...
- Acwing算法—动态规划
目录 数字三角形模型 AcWing 898. 数字三角形 AcWing 1015. 摘花生 AcWing 1018. 最低通行费 AcWing 1027. 方格取数 AcWing 275. 传纸条 最 ...
- 算法——AcWing算法提高课中代码和题解
文章目录 第一章 动态规划 (完成情况:64/68) 数字三角形模型 最长上升子序列模型 背包模型 状态机模型 状态压缩DP 区间DP 树形DP 数位DP 单调队列优化DP 斜率优化DP 第二章 搜索 ...
- AcWing算法提高课
1. 动态规划(43/68) 1.1 数字三角形模型(4/4) 1.1.1 AcWing 1015. 摘花生 结论: f[i][j]=max(f[i−1][j],f[i][j−1])+w[i][j] ...
- 【动态规划】状态机模型
整理的算法模板合集: ACM模板 文章目录 A.抛砖引玉 - AcWing 1049. 大盗阿福 B.AcWing 1057. 股票买卖 IV C.AcWing 1058. 股票买卖 V D.AcWi ...
最新文章
- 豆瓣评分9.3,陪伴无数程序员成长的神作,终于升级了!
- Application Loader:上传卡在App Store正在通过iTunes Store鉴定
- 史上最硬核的Linux依赖问题解决方案
- 从godaddy转出域名
- redis 源码 object.c 实现
- lan pci 联想开机_微软承认KB4568831导致部分联想ThinkPad笔记本崩溃和蓝屏
- MySQL 主从复制 复制过滤
- lamp怎么使用mysql_lamp(四)mysql操作
- 【TensorFlow-windows】(七) CNN之VGG-net的测试
- android 速度传感器,Android实战技巧之四十二:加速度传感器
- [转载收藏]三层式开发中的层次划分
- android开发 修改标题栏背景_Android哆啦A梦调试工具体验
- C++实现演讲比赛小项目
- 【UVALive - 7344】Numbered Cards【数位DP+状压DP】
- ps切图怎么做成html,PS切图怎么导出网页 PS切图怎么生成源代码
- jQuery TagsInput
- 我们为什么需要实时数据库?
- 即构推出微信引流方案,助力在线教育平台大幅降低获客成本
- python 股票交易接口 github_GitHub - Higgsbit/vnpy: 基于python的开源交易平台开发框架...
- Android高仿微信头像裁剪
热门文章
- Pytorch中retain_graph参数的作用
- FastDFS上传文件失败原因
- 大数据之Hive:greatest和least函数
- 升级High Sierra惊魂记
- 用Fragment实现图片简易浏览
- Win7系统怎么删除休眠文件?
- 使用SSH客户端远程登录Linux主机(可替代samba、ftp服务)
- textarea 属性placeholder
- 为什么用python扒取出来的数据为空列表_如何解决python xpath爬取页面得到空列表(语法都对的情况下)...
- 最懂区块链的十大女神,值得你关注 | 年度盘点