Loading

题解—[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\)

\[\large f[i+1][tr [j][p]]+=f[i][j] \]

这里注意一个事情\(\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\)数组。
然后我们就变成普通的暴力了,可以列出来式子。

\[\large f[i][j]=\sum_{k=0}^{m} f[i-1][k]*g[k][j] \]

这是个很显然的矩阵优化吧,直接莽一下\(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\)一起排在最后一页(反向登榜,法力无边)
image

posted @ 2021-06-29 21:43  Soresen  阅读(85)  评论(2)    收藏  举报