【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):主键就是每个数据行(记录)的唯一标识,不会有重复值的列(字段)才能当做主键.一个表可以没有主键,但是这样会很难处理表,因此一般情况表都要设置主键. 主键有两张选用策略,分 ...
随机推荐
- PHP高端课程
关于目后佐道IT教育 http://www.cnblogs.com/itpua/p/7710917.html 目后佐道IT教育的师资团队 http://www.cnblogs.com/itpua/p/ ...
- 自己太水了—HDOJ_2212
Problem Description A DFS(digital factorial sum) number is found by summing the factorial of every d ...
- 分布式文件系统ceph介绍
ceph哲学思想 1. 每个组件必须支持扩展 2.不存在单点故障 3.解决方案必须是基于软件的.开源的.适应能力强 4.任何可能的一切必须自我管理 存在的意义:帮助企业摆脱昂贵的专属硬件 ceph目标 ...
- Spring启动流程—源码解读
https://blog.csdn.net/yangliuhbhd/article/details/80790761 Spring的AbstractApplicationContext的refresh ...
- SpringBoot整合升级Spring Security 报错 【The request was rejected because the URL was not normalized】
前言 最近LZ给项目框架升级, 从Spring1.x升级到Spring2.x, 在这里就不多赘述两个版本之间的区别以及升级的原因. 关于升级过程中踩的坑,在其他博文中会做比较详细的记录,以便给读者参考 ...
- Taro:使用taro完成小程序开发
前言:taro是一个可以很好实现一次开发,多端统一的框架,本文只介绍它小程序端开发的一些内容.小程序项目搭建gitup已经有很清楚的说明:https://github.com/NervJS/taro ...
- Could not connect to Redis at IP No route to host
这个问题是在用远程去访问redis出现的 原因:是服务器新装系统 iptables这个的问题 解决办法: sudo iptables -F 轻松解决
- python--进程内容补充
一. 进程的其他方法 进程id, 进程名字, 查看进程是否活着(is_alive()), terminate()发送结束进程的信号 import time import os from multipr ...
- Python的第二堂课(2)
一.初探python print('Hello,靓仔!') 不得不说,这句话还是so real的(逃 二.Python中的变量 1.什么是变量?(what) 量:记录某种现实世界中事物的某种状态: 变 ...
- 10大mysql需要注意的参数
MySQL变量很多,其中有一些MySQL变量非常值得我们注意,下面就为您介绍一些值得我们重点学习的MySQL变量,供您参考. 1 Threads_connected 首先需要注意的,想得到这个变量的值 ...