【2018.10.10】[HNOI2008] GT考试(bzoj1009)
10pts:
暴力枚举字符串,Hash判是否出现。(真会有人写么)
时间复杂度$O(10^n*n)$。
40pts:
学过OI的人都会写的dp
如果这道题的40pts($n\le 250000$)设成100pts的话那就是水的一批的提高组题目了。可惜这是省选题目
设$f(i,j)$表示以准考证号为基准递推,准考证号匹配到第$i$位,不吉利数字匹配到第$j$位时(即准考证的后$j$位等于不吉利数字的前$j$位),不出现不吉利数字的字符串数量。
发现$m$只有20,$n*m$的dp可过。
那怎么转移?
既然要以考号为基准递推,就要考虑一位考号对下一位的影响。
而考号是可以随便写的,那我们就要考虑10种数字了。
对于一个新数字$new$,有以下几种情况:
1.$new$与不吉利数字的$j+1$位匹配,$dp(i+1,j+1)$的答案数+dp(i,j)
2.上述两者不匹配。
不匹配怎么办?这个不匹配的$new$一定没有贡献了?
当然不一定。
你有没有听说过一个叫$AC$自动机的垃圾。
啥,你没听说过?那放水点,你有没有听说过一个叫$KMP$的垃圾。
可能存在与$new$后缀与它相同的不吉利数字前缀,而这些前缀位置是要被更新答案的。
怎么讲呢,举个例子吧,不吉利数字是$12212112$。
然后你的准考证号枚举到第$9$位时,前面$8$位已经枚举成了$11112212$。可以发现已经匹配了不吉利数字的前$5$位,现在要匹配第$6$位。
如果第$9$位枚举$1$,那它就匹配了,$dp(9,6)+=dp(8,5)$。
如果第$9$位枚举$2$,那它就不匹配。但是会发现,存在 与当前已匹配的不吉利数字的后缀相同 的前缀,可以匹配上这个$2$!
上一个位置就是不吉利数字的第$2$位。
它的下一位,第$3$位$2$,刚好可以匹配枚举的第$9$位!
所以此时最多能匹配不吉利数字的前$3$位,有转移$dp(9,3)+=dp(8,5)$。
2019.8.23 update:这段好像解释错了,建议无视
我们只需要沿着不吉利数字的失配指针往前走,找到第一个下一位与$new$匹配的位置就可以了。
为什么不用考虑再往前的下一位可以匹配$new$的位置?
因为这是递推,从前往后每一种状态都会被考虑,所以在考虑匹配后面的位之前,前面的位已经匹配好更前面的情况了。
比如不吉利数字$1221221211$。你目前枚举的准考证号前$8$位是$12212212$,现在你要枚举第$9$位。
很明显当枚举$2$时,通过找不吉利数字中 后缀相同的前缀,可知$dp(8,8)$可以转移到$dp(9,6)$。
但是我们发现也可以转移到$dp(9,3)$诶!
事实上,在这之前$dp(8,5)$已经转移到$dp(9,3)$过了。而$dp(8,5)$表示什么?它表示准考证号枚举8位,后$5$位与不吉利数字的前$5$位匹配上。
$dp(8,8)$同理,表示准考证号枚举前$8$位,后$8$位与不吉利数字的前$5$位匹配上。
这样直观看起来没什么答案关联。但是仔细考虑以下,不吉利数字的第$8$位的失配指针指向第$5$位。
这说明什么?
不吉利数字的前$8$位中,前$5$位等于后$5$位!
还不够直观,能再明白点么?
匹配$5$位的情况包含匹配$8$位的情况!(因为你匹配了$8$位,根据上推论可知也算在匹配了$5$位的情况中)
这就是别人博客中此题题解经常提到的计数方案会包含的问题。蒟蒻之前也一直没看懂,想了一通才明白……
所以我们在转移时,把$dp(8,8)$的情况加给$dp(8,5)$,然后让$dp(8,5)$捎带加给$dp(9,3)$。
其实按照$AC$自动机的构造方式,如果一个点没有字符为$new$的儿子边的话,它会建出一条对应的虚拟儿子边和点,儿子点上存的是沿着失配指针往回走的上一个实际存在这条字符边所指向的儿子。
当然这题$m$很小,不吉利数字串自己匹配自己的复杂度很小,可以直接暴力跑失配指针找第一个。
转移就这样
$dp[i+1][j]=\sum_{0<=k<=m-1}dp[i][k] \times g[k][j]$
其中$g(i,j)$表示准考证号匹配不吉利数字的前$i$位时,准考证号增加一个字符,使不吉利数字沿失配指针(自己也可以)找到的最大的匹配位数$j$(算上新匹配的一位)的方案数。
$dp$套$dp$?
其实不用,不吉利数字已经知道了,$g$数组可以预处理出来($KMP$)。
注意一个事情,沿着失配指针走时,可能找不到与准考证号枚举位匹配的位置。此时新的最大匹配位数是$0$,仍然可以转移!
时间复杂度$O(n*m^2)$。
100pts:
然后我们惊奇地发现$n\le 10^9$,不让你循环推,直接就想到矩阵快速幂优化了。
观察一下转移方程,发现$dp[i+1][?]$总是由$dp[i][?]$推来,而且$i$还是$n$这个级别的。
又发现每次实际上都是乘一个固定的矩阵$g$,也就是说整个$dp$数组的某一位的值其实都是通过一些$g$数组乘过来的。
所以可以把$dp$数组直接当成$g$数组自己乘自己。
比如说,$g(4,2)=4$。
表示有4种转移情况可以让 与不吉利数字的前4位匹配的情况 在准考证号增加1位后 与不吉利数字的前2位匹配。
所以$dp(x+1,2)=dp(x,4)*g(4,2)=dp(x,4)*4$ | $x$是正整数。
而$dp(x,4)$又是哪来的?
它是通过$\sum_{i=1}^{m}dp(x-1,i)*g(i,4)$转移过来的。
所以一直往前推到x=0,发现$dp$值的$n$次转移都只跟转移矩阵$g$有关,是否与不吉利数字完全匹配等情况可以在弄$g$数组时就处理掉,即$g$数组只考虑不吉利数字被匹配$0$~$m-1$位的情况,不让它转移到$j$位都被匹配的情况。
所以就是对$g$矩阵做快速幂,求它的$n$次方。
时间复杂度$O(log(n)*m^3)$,有点常数。
最后说明:
1. 为什么答案是$\sum_{i=0}^{j-1}g(0,i)$:
考虑$g(i,j)$的意义。如果$g$矩阵自己对自己连续进行$k$次转移(不进行快速幂而循环推),$g(i,j)$就表示:在进行$k$次转移前 准考证号匹配不吉利数字的前$i$位时,准考证号增加$k$个字符后,使不吉利数字沿失配指针(自己也可以)找到的最大的匹配位数$j$(算上新匹配的$k$位)的方案数。这是矩阵转移的基本概念。
所以进行$n$次转移后,$g(i,j)$就表示一开始准考证号匹配不吉利数字的前$i$位时,准考证号增加$n$个字符后,使不吉利数字沿失配指针(自己也可以)找到的最大的匹配位数$j$(算上新匹配的$k$位)的方案数。
我们需要取全局情况的答案,而这是很显然的。开始时准考证号匹配不吉利数字的前$0$位(准考证号还一位都没枚举),所以$i$为$0$;而由于已经定义过$g$数组只考虑不吉利数字被匹配 $0$~$m-1$ 位的情况,所以$j$在这个区间取任意值,$g(0,j)$都是答案的一部分。把它们都算上就是答案。
2. (2018.11.26 update)初始矩阵是一维线性矩阵,而不是二维的!之前一直以为是二维的导致理解很麻烦!
下面是code,不过我的转移矩阵$b$(就是$g$)的$i,j$维是反过来写的,即 匹配后的匹配位数 指向 匹配前的匹配位数。
#include<bits/stdc++.h>
#define M 21
#define ll long long
using namespace std;
inline int read(){
int x=; bool f=; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=;
for(; isdigit(c);c=getchar()) x=(x<<)+(x<<)+(c^'');
if(f) return x;
return -x;
}
int n,m,mod,Fail[M];
char ch[M];
ll a[M][M],b[M][M];
void mul(ll a[M][M],ll b[M][M]){
ll tmp[M][M];
for(int i=;i<m;++i)
for(int j=;j<m;++j){
tmp[i][j]=;
for(int k=;k<m;++k)
(tmp[i][j]+=a[i][k]*b[k][j])%=mod;
}
for(int i=;i<m;++i)
for(int j=;j<m;++j)
a[i][j]=tmp[i][j];
}
int main(){
n=read(),m=read(),mod=read();
scanf("%s",ch+);
int i,j=;
for(i=;i<=m;++i){
while(j> && ch[j+]!=ch[i]) j=Fail[j];
if(ch[j+]==ch[i]) ++j;
Fail[i]=j;
}
int t;
for(i=;i<m;++i)
for(j=;j^;++j){
t=i;
while(t> && ch[t+]-''!=j) t=Fail[t];
if(ch[t+]-''==j) ++t;
if(t^m) b[t][i]=(b[t][i]+)%mod;
}
for(i=;i<m;++i) a[i][i]=;
while(n){
if(n&) mul(a,b);
mul(b,b);
n>>=;
}
ll sum=;
for(i=;i^m;++i) (sum+=a[i][])%=mod;
printf("%lld\n",sum);
return ;
}
【2018.10.10】[HNOI2008] GT考试(bzoj1009)的更多相关文章
- BZOJ1009 [HNOI2008]GT考试 矩阵
去博客园看该题解 题目 [bzoj1009][HNOI2008]GT考试 Description 阿申准备报名参加GT考试,准考证号为N位数X1X2….Xn(0<=Xi<=9),他不希望准 ...
- 【BZOJ1009】[HNOI2008]GT考试 next数组+矩阵乘法
[BZOJ1009][HNOI2008]GT考试 Description 阿申准备报名参加GT考试,准考证号为N位数X1X2....Xn(0<=Xi<=9),他不希望准考证号上出现不吉利的 ...
- [BZOJ1009] [HNOI2008] GT考试(KMP+dp+矩阵快速幂)
[BZOJ1009] [HNOI2008] GT考试(KMP+dp+矩阵快速幂) 题面 阿申准备报名参加GT考试,准考证号为N位数X1X2-.Xn,他不希望准考证号上出现不吉利的数字.他的不吉利数学A ...
- 【bzoj1009】[HNOI2008]GT考试
1009: [HNOI2008]GT考试 Time Limit: 1 Sec Memory Limit: 162 MBSubmit: 3018 Solved: 1856[Submit][Statu ...
- bzoj1009 [HNOI2008] GT考试 矩阵乘法+dp+kmp
1009: [HNOI2008]GT考试 Time Limit: 1 Sec Memory Limit: 162 MBSubmit: 4542 Solved: 2815[Submit][Statu ...
- [Bzoj1009][HNOI2008]GT考试(KMP)(矩乘优化DP)
1009: [HNOI2008]GT考试 Time Limit: 1 Sec Memory Limit: 162 MBSubmit: 4309 Solved: 2640[Submit][Statu ...
- 申请Office 365一年免费的开发者账号攻略(2018年10月份版本)
要进行Office 365开发,当然需要有完整的Office 365环境才可以.为了便于广大开发人员快速地启动这项工作,微软官方给所有开发人员提供了免费的一年开发者账号 那么如何申请Office ...
- IntelliJ IDEA 最新激活码(截止到2018年10月14日)
IntelliJ IDEA 注册码: EB101IWSWD-eyJsaWNlbnNlSWQiOiJFQjEwMUlXU1dEIiwibGljZW5zZWVOYW1lIjoibGFuIHl1IiwiYX ...
- 新手C#SQL Server使用记录2018.08.10
主键(PrimaryKey):主键就是每个数据行(记录)的唯一标识,不会有重复值的列(字段)才能当做主键.一个表可以没有主键,但是这样会很难处理表,因此一般情况表都要设置主键. 主键有两张选用策略,分 ...
随机推荐
- Django之CSRF问题
1.csrf全称:cross site request forgery(跨站请求伪造),举例来讲,一个安全的网站A,一个恶意网站B,当你在A网站进行了登录后,这时候浏览器会保存你的cookie和ses ...
- hdu 3555 Bomb 炸弹(数位DP,入门)
题意: 给一个数字n,求从1~n中有多少个数是含有49的,比如49,149,1490等都是含49的. 思路: 2^64也顶多是十进制的20多位,那么按十进制位来分析更简单.如果能计算k位十进制数中分别 ...
- 使用JDK自带的工具jstack找出造成运行程序死锁的原因
Java多线程编程也是Java面试中经常考察的内容.刚接触Java多线程编程的朋友们,可能会不慎写出一些会导致死锁(deadlock)的应用出来.如何分析造成Java多线程的原因呢?很多时候我们在怀疑 ...
- 一个具体的例子学习Java volatile关键字
相信大多数Java程序员都学习过volatile这个关键字的用法.百度百科上对volatile的定义: volatile是一个类型修饰符(type specifier),被设计用来修饰被不同线程访问和 ...
- 【GIMP学习】抠图方法二则
之前抠图都比较二,懒人我尝试过在线抠图软件.以及在线PS简易版,真的都很不好用,前者简单粗暴,后者我遇到各种储存不能的bug. 在ubuntu的环境下有一个功能可以和PS相媲美的功能强大图片处理软件G ...
- SQLite-表达式
SQLite -表达式 一个表达式是一个或多个值的组合,运算符和SQL函数,评价一个值. SQL表达式就像公式和都写在查询语言.您还可以使用为特定的数据集查询数据库. 语法: 考虑到SELECT语句的 ...
- PyCharm如何配置断点调试功能
1. 点击菜单 PyCharm -> Preferences.. 2. 在左侧菜单栏找到Project:Django - > Project Interpreter 并点击配置 Proje ...
- Hopfield 网络(上)
讲的什么 这部分主要对 Hopfield 网络作一大概的介绍.写了其模型结构.能量函数和网络的动作方式.主要参考了网上搜到的一些相关 PPT. 概述 早在 1982 年,Hopfield 发表的文 ...
- C++ C++ 值传递、指针传递、引用传递详解
这一篇博客写的不错: https://www.cnblogs.com/dingxiaoqiang/p/8012578.html
- QT+动手设计一个登陆窗口+布局
登陆窗口的样式如下: 这里面涉及着窗口的UI设计,重点是局部布局和整体布局, 首先在ui窗口上添加一个容器类(Widget),然后将需要添加的控件放置在容器中,进行局部布局(在进行局部布局的时候可以使 ...