题解—[HNOI2008]GT考试($kmp$自动机)
为什么\(HNOI\)的题都是虐我千百遍啊。(手动联想卡农)
这个题我一开始就打对了,然后由于打的时候思路有点不清楚,打的有点鬼畜。
导致没有过样例,让我以为我的正解思路是错的。
先搞暴力
大部分暴力是\(O(n*m^2)\)的,我的暴力是\(O(n*m*|G|)\),\(|G|\)是字符集大小,在本题为\(10\)。
由于我是刚做完\(AC\)自动机,感觉这东西很像\(AC\)自动机上\(dp\)啊,然后就像把一个串插进去,搞个\(AC\)自动机。
实际上没必要,有一个东西叫做\(kmp\)自动机,其实就是一个串的\(AC\)自动机。
实现很简单,求出来\(next\)数组,直接跟\(AC\)自动机差不多的构建就行了。
他的实际含义\(tr[j][p]\)代表匹配到\(j\)位的时候,加上一个字符\(p\),匹配几位。
贴个代码,防忘
void build(){
int len = strlen( s + 1 ) ;
for( R i = 2 , j = 0 ; i <= len ; i ++ ){
while( j && s [i] != s [j + 1] ) j = fail [j] ;
if( s [i] == s [j + 1] ) fail [i] = ++ j ;
else fail [i] = 0 ;
}
for( R i = 0 ; i < len ; i ++ )
for( R j = 0 ; j <= 9 ; j ++ ){
int ch = s [i + 1] - '0' ;
if( ch == j ) tr [i][j] = i + 1 ;
else tr [i][j] = tr [fail [i]][j] ;
}
}
有了这个自动机,我们就可以快乐转移了,对于每个\(f[i][j]\),我们枚举\(0~9\)
这里注意一个事情\(\Huge{千万不要跟某个智障一样转移f[i][m]}\)。
我也不知道怎么想的,当时顺手打了一个\(j<=m\),然后就自闭了,甚至沦落到了看题解的地步(虽然没看懂又回来了)
原因显然,\(f[i][m]\)已经是不合法状态了,转移他只会让我们得到错误的答案。
暴力搞完,已经可以得到\(40pts\)的好成绩了(跟朴素暴力一样,因为\(m\)太小了)
接着搞正解
如果你用\(kmp\)自动机,那么整个思路过程将非常顺滑,没有跳跃。
我们发现,我们并不关心他的具体字符,所以我们可以记录有几个字符可以让他从长度为\(i\)转移到长度为\(j\)。
这个非常好实现,只需要在build函数中,添加这样一段代码。
for( R i = 0 ; i <= len ; i ++ )
for( R j = 0 ; j <= 9 ; j ++ )
g [i][tr [i][j]] ++ ;
我们就得到了大多数题解中直接蹦出来的\(g\)数组。
然后我们就变成普通的暴力了,可以列出来式子。
这是个很显然的矩阵优化吧,直接莽一下\(O(n*m^2)\)就变成\(O(logn*m^3)\)
不过值得一提的是这种矩阵的写法常数是真的小
(要不我就变成倒数第一了(小声))
记录一下
小常数矩阵快速幂写法
inline void mul( int a [23] , int b [23][23] ){
int c [23] ;
for( R i = 0 ; i < 23 ; i ++ ) c [i] = 0 ; //memset 0
for( R i = 0 ; i < m ; i ++ )
for( R j = 0 ; j < m ; j ++ )
c [i] = ( 0ll + c [i] + 1ll * a [j] * b [j][i] ) % K ;
memcpy( a , c , sizeof( c ) ) ;
}
inline void mulself( int a [23][23] ){
int c [23][23] ;
for( R i = 0 ; i < 23 ; i ++ ) for( R j = 0 ; j < 23 ; j ++ ) c [i][j] = 0 ; //memset 0
for( R i = 0 ; i < m ; i ++ )
for( R j = 0 ; j < m ; j ++ )
for( R k = 0 ; k < m ; k ++ )
c [i][j] = ( 0ll + c [i][j] + 1ll * a [i][k] * a [k][j] ) % K ;
memcpy( a , c , sizeof( c ) ) ;
}
// in main
for( ; n ; n >>= 1 ){
if( n & 1 ) mul( f , g ) ;
mulself( g ) ;
}
AC code
#include <cstring>
#include <algorithm>
#include <cstdio>
#define mp make_pair
#define R register int
#define int long
#define printf Ruusupuu = printf
#define scanf Ruusupuu = scanf
int Ruusupuu ;
using namespace std ;
typedef long long L ;
typedef long double D ;
typedef unsigned long long G ;
typedef pair< int , int > PI ;
const int N = 2e5 + 10 ;
inline int read(){
int w = 0 ; bool fg = 0 ; char ch = getchar() ;
while( ch < '0' || ch > '9' ) fg |= ( ch == '-' ) , ch = getchar() ;
while( ch >= '0' && ch <= '9' ) w = ( w << 1 ) + ( w << 3 ) + ( ch ^ '0' ) , ch = getchar() ;
return fg ? -w : w ;
}
int n , m , K , fail [23] , tr [23][11] , f [23] , g [23][23] ;
char s [30] ;
void sc(){
n = read() , m = read() , K = read() ;
scanf( "%s" , s + 1 ) ;
}
void build(){
int len = strlen( s + 1 ) ;
for( R i = 2 , j = 0 ; i <= len ; i ++ ){
while( j && s [i] != s [j + 1] ) j = fail [j] ;
if( s [i] == s [j + 1] ) fail [i] = ++ j ;
else fail [i] = 0 ;
}
//for( R i = 1 ; i <= len ; i ++ ) printf( "%ld\n" , fail [i] ) ;
for( R i = 0 ; i < len ; i ++ ){
for( R j = 0 ; j <= 9 ; j ++ ){
int ch = s [i + 1] - '0' ;// printf( "%ld %ld\n" , i , ch ) ;
if( ch == j ) tr [i][j] = i + 1 ;
else tr [i][j] = tr [fail [i]][j] ;
}
}
for( R i = 0 ; i <= len ; i ++ )
for( R j = 0 ; j <= 9 ; j ++ )
g [i][tr [i][j]] ++ ;
// for( R i = 0 ; i <= len ; i ++ )
// for( R j = 0 ; j <= len ; j ++ )
// printf( "%ld %ld %ld\n" , i , j , g [i][j] ) ;
}
void mul( int a [23] , int b [23][23] ){
int c [23] ; for( R i = 0 ; i < 23 ; i ++ ) c [i] = 0 ;
for( R i = 0 ; i < m ; i ++ )
for( R j = 0 ; j < m ; j ++ )
c [i] = ( 0ll + c [i] + 1ll * a [j] * b [j][i] ) % K ;
// for( R i = 1 ; i <= m ; i ++ ) printf( "%ld\n" , a [i] ) ;
memcpy( a , c , sizeof( c ) ) ;
}
void mulself( int a [23][23] ){
int b [23][23] ; for( R i = 0 ; i < 23 ; i ++ ) for( R j = 0 ; j < 23 ; j ++ ) b [i][j] = 0 ;
for( R i = 0 ; i < m ; i ++ )
for( R j = 0 ; j < m ; j ++ )
for( R k = 0 ; k < m ; k ++ )
b [i][j] = ( 0ll + b [i][j] + 1ll * a [i][k] * a [k][j] ) % K ;
memcpy( a , b , sizeof( b ) ) ;
}
void work(){
build() ;
f [0] = 1 ;
for( ; n ; n >>= 1 ){
if( n & 1 ) mul( f , g ) ;
mulself( g ) ;
}
/* for( R i = 1 ; i <= m ; i ++ )
for( R j = 1 ; j <= m ; j ++ )
printf( "%ld %ld %ld\n" , i , j , g [i][j] ) ;
for( R i = 1 ; i <= m ; i ++ )
printf( "%ld\n" , f [i] ) ;*/
int ans = 0 ;
for( R i = 0 ; i < m ; i ++ ) ans = ( ans + f [i] ) % K ;
printf( "%ld\n" , ans ) ;
}
signed main(){
sc() ;
work() ;
return 0 ;
}
由于做法中有一些没必要的步骤,所以和做法更奇怪的\(ICEY\)一起排在最后一页(反向登榜,法力无边)


浙公网安备 33010602011771号