Loading

文本生成器,密码—题解

两道比较相似的\(AC\)自动机上\(dp\),并且我的做法好像跟题解都不一样。

密码

这个\(dp\),还是挺好设计的。
因为\(AC\)自动机上\(dp\),一般肯定有一维是当前在自动机的某个节点上。
然后发现得状压,因为他让包含所有的串,并且\(N\)很小。
那就直接设计出来了,\(f[i][j][k]\)表示长度为\(i\)的串在自动机的\(j\)节点上时状态为\(k\)的方案数。
转移只要想一想就可以,一般\(AC\)自动机上的\(dp\)都是枚举字符转移的。

\[f[i+1][tr[j][p]][k | st[tr[j][p]]]+=f[i][j][k] \]

其中拿\(st\)存每个节点的状态,很经典的在\(build\)自动机的时候继承信息就行了。
最后答案很显然是\(\sum_{i=0}^{tnt}f[L][i][(1<<N)-1]\)
输出方案最后再说

文本生成器

这题思路跟各位大佬的思路很不一样,感觉我的思路更容易想到一些。

本人思路

这个\(dp\),还是挺好设计的。(貌似在哪见过这句话)
因为\(AC\)自动机上\(dp\),一般肯定有一维是当前在自动机的某个节点上。(同上)
还是上一题的思想,上一题我们可以统计所有\((1<<N)-1\)的答案,这一题怎么办呢?
好纸张的问题,这不是裸的吗,直接设计一维代表他当前到没到带有\(end\)的节点。
还是经典的\(AC\)自动机上从\(fail\)继承信息,就可以知道每个节点是不是一个单词的结尾。
然后类比上题设计状态\(f[i][j][k]\)表示长度为\(i\),在自动机的\(j\)节点上,\(k\)表示当前有没有走到过\(end\)节点,\(0\)代表没有,\(1\)代表有。
然后方程非常好设计

\[f[i+1][tr[j][p]][k|end[tr[j][p]]]+=f[i][j][k] \]

最后答案就是\(\sum_{i=0}^{tnt}f[m][i][1]\)

code


#include <cstring>
#include <algorithm>
#include <cstdio>
#include <queue>
#define mp make_pair
#define R register int
#define int long 
#define printf Ruusupuu = printf

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 = 1e2 + 10 ;
const int K = 6e3 + 10 ;
const int C = 27 ;
const int M = 1e4 + 7 ; 

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 ; 
}

inline void wap( int &a , int &b ){ a = a ^ b , b = a ^ b , a = a ^ b ; }
inline int mins( int a , int b ){ int zt = b - a ; return a + ( zt & ( zt >> 31 ) ) ; }
inline int maxs( int a , int b ){ int zt = b - a ; return b - ( zt & ( zt >> 31 ) ) ; }
inline int abss( int a ){ int zt = a >> 31 ; return ( a + zt ) ^ zt ; }

int n , m , tr [K][C] , fail [K] , cnt , f [N][K][2] , ans ;
char s [N] ; bool end [K] ;

inline void ins( char s [] ){
	int len = strlen( s + 1 ) , x = 0 ;
	for( R i = 1 ; i <= len ; i ++ ){
		int ch = s [i] - 'A' ;
		if( !tr [x][ch] ) tr [x][ch] = ++ cnt ;
			x = tr [x][ch] ;
	} end [x] = 1 ;
} 

void build(){
//	for( R i = 0 ; i <= cnt ; i ++ ) 
//		for( R j = 0 ; j < 26 ; j ++ )
//			printf( "%ld %ld %ld\n" , i , j , tr [i][j] ) ;
	queue< int > q ;
	for( R i = 0 ; i < 26 ; i ++ ) if( tr [0][i] ) q.push( tr [0][i] ) ;
	
	while( !q.empty() ){
		int x = q.front() ; q.pop() ;
		for( R i = 0 ; i < 26 ; i ++ ){
			if( tr [x][i] ) fail [tr [x][i]] = tr [fail [x]][i] , end [tr [x][i]] |= end [tr [fail [x]][i]] , q.push( tr [x][i] ) ;	
			else tr [x][i] = tr [fail [x]][i] ;
		}
	} 
//	for( R i = 0 ; i <= cnt ; i ++ ) 
//		for( R j = 0 ; j < 26 ; j ++ )
//			printf( "%ld %ld %ld\n" , i , j , tr [i][j] ) ;	
}

void sc(){
	n = read() , m = read() ;
	for( R i = 1 ; i <= n ; i ++ ) 
		scanf( "%s" , s + 1 ) , ins( s ) ;
	build() ; 
}

void work(){
	f [0][0][0] = 1 ; 
	for( R i = 0 ; i < m ; i ++ )
		for( R j = 0 ; j <= cnt ; j ++ )
			for( R k = 0 ; k < 26 ; k ++ )
				for( R l = 0 ; l <= 1 ; l ++ )
					f [i + 1][tr [j][k]][l | end [tr [j][k]]] = ( f [i + 1][tr [j][k]][l | end [tr [j][k]]] + f [i][j][l] ) % M ;
	for( R i = 0 ; i <= cnt ; i ++ ) ans = ( ans + f [m][i][1] ) % M ;
	printf( "%ld\n" , ans ) ;
}

signed main(){	
	sc() ;
	work() ;
	return 0 ;
} 

题解思路

统计所有不合法的,最后用\(26^m\)减去就可以了。
和上文一样先搞出来\(end\)标记。
然后如果\(tr[j][p]\)有标记,就不往他那转移(这个思路还是很不错的)
比我的做法快一倍,因为少一个\(2\)的常数。
题解\(link\)

密码输出方案

我的做法

我的做法一看就很伪,不过水过去了。
最短母串一样的思路,就是搜到他输出完为止。
但是有一个问题,\(f[i][j][k]\)代表的状态并不唯一。
所以我们可以让一个点经过多次,实测只要让他经过小于等于\(10\)次就可以\(AC\)
本题因为要输出所有方案,所以\(dfs\)\(bfs\)都可以。

这个思路还是很值得记录一下的,如果一个点对应的状态不止一个但不多,而我们想不到如何设计新的状态来避免他重复的话,可以让一个状态经过一定的次数,至于这个次数,本地造点大数据调一下参数就可以了。

Dfs code
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <queue>
#include <iostream>
#include <bitset>
#define mp make_pair
#define R register int
#define int long 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 = 1e2 + 10 ;
const int E = 1e1 + 2 ;
const int M = N * ( 1 << E ) ; 

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 ; 
}

inline void wap( int &a , int &b ){ a = a ^ b , b = a ^ b , a = a ^ b ; }
inline int mins( int a , int b ){ int zt = b - a ; return a + ( zt & ( zt >> 31 ) ) ; }
inline int maxs( int a , int b ){ int zt = b - a ; return b - ( zt & ( zt >> 31 ) ) ; }
inline int abss( int a ){ int zt = a >> 31 ; return ( a + zt ) ^ zt ; }
int n ,	l , cnt , anss , tr [M][27] , st [M] , fail [M] , ans , f [10 * E][1 << E][N] , top ;
int fg [E][N][1 << E] ; char s [N] ;

struct P{ char s [N] ; int len ; } m [50] ;

inline void ins( char s [] , int ix ){
	int len = strlen( s + 1 ) , x = 0 ;
	for( R i = 1 ; i <= len ; i ++ ){
		int ch = s [i] - 'a' ;
		if( !tr [x][ch] ) tr [x][ch] = ++ cnt ;
		x = tr [x][ch] ;
	} st [x] |= ( 1 << ( ix - 1 ) ) ;
}

void build(){
	queue< int > q ;
	for( R i = 0 ; i < 26 ; i ++ ) if( tr [0][i] ) q.push( tr [0][i] ) ;
	
	while( !q.empty() ){
		int x = q.front() ; q.pop() ;
		for( R i = 0 ; i < 26 ; i ++ ){
			if( tr [x][i] ) fail [tr [x][i]] = tr [fail [x]][i] , st [tr [x][i]] |= st [tr [fail [x]][i]] , q.push( tr [x][i] ) ;
			else tr [x][i] = tr [fail [x]][i] ;
		}
	}
}

inline void debug( int x ){ cout << bitset<10> (x) << endl ; }

void dfs( int len , int x , int sa ){
	if( len > l ) return ;
	if( sa == ( 1 << n ) - 1 && len == l ){
		for( R i = 1 ; i <= l ; i ++ )
			printf( "%c" , s [i] ) ;
		puts( "" ) ; return ;
	} 
	for( R i = 0 ; i < 26 ; i ++ ){
		if( fg [len + 1][tr [x][i]][sa | st [tr [x][i]]] <= 10 ){
			fg [len + 1][tr [x][i]][sa | st [tr [x][i]]] ++ ;
			s [len + 1] = i + 'a' ; 
			dfs( len + 1 , tr [x][i] , sa | st [tr [x][i]] ) ;
		}
	}
}

void sc(){
	l = read() , n = read() ;
	for( R i = 1 ; i <= n ; i ++ ) 
		scanf( "%s" , m [i].s + 1 ) , ins( m [i].s , i ) ;
}

void work(){
	build() ;
	f [0][0][0] = 1 ;
	for( R i = 0 ; i < l ; i ++ )
		for( R j = 0 ; j < ( 1 << n ) ; j ++ )
			for( R k = 0 ; k <= cnt ; k ++ )
				for( R p = 0 ; p < 26 ; p ++ )
				f [i + 1][j | st [tr [k][p]]][tr [k][p]] += f [i][j][k] ;
	for( R i = 0 ; i <= cnt ; i ++ ) anss += f [l][( 1 << n ) - 1][i] ;
	printf( "%lld\n" , anss ) ;		
	if( anss <= 42 ) fg [0][0][0] = 1 , dfs( 0 , 0 , 0 ) ;
}

signed main(){	
	sc() ;
	work() ;
	return 0 ;
}

Bfs code
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <queue>
#define mp make_pair
#define R register int
#define int long long
#define printf Ruusupuu = printf

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 = 1e2 + 10 ;
const int E = 1e1 + 2 ;
const int M = N * E * ( 1 << E ) ; 

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 ; 
}

inline void wap( int &a , int &b ){ a = a ^ b , b = a ^ b , a = a ^ b ; }
inline int mins( int a , int b ){ int zt = b - a ; return a + ( zt & ( zt >> 31 ) ) ; }
inline int maxs( int a , int b ){ int zt = b - a ; return b - ( zt & ( zt >> 31 ) ) ; }
inline int abss( int a ){ int zt = a >> 31 ; return ( a + zt ) ^ zt ; }

int n ,	l , cnt , anss , tr [M][27] , st [M] , fail [M] , ans , ciner , chr [M] , fa [M] , alls , f [3 * E][1 << E][N] ;
int fg [N][N][1 << E] ;

struct P{ char s [N] ; int len ; } m [50] ;

inline bool cmp ( P a , P b ){
	int alen = min( a.len , b.len ) ;
	for( R i = 1 ; i <= alen ; i ++ ){
		if( a.s [i] < b.s [i] ) return 1 ;
		if( a.s [i] > b.s [i] ) return 0 ; 
	} return a.len < b.len ; 
}

inline void ins( char s [] , int ix ){
	int len = strlen( s + 1 ) , x = 0 ;
	for( R i = 1 ; i <= len ; i ++ ){
		int ch = s [i] - 'a' ;
		if( !tr [x][ch] ) tr [x][ch] = ++ cnt ;
		x = tr [x][ch] ;
	} st [x] |= ( 1 << ( ix - 1 ) ) ;
}

void build(){
	queue< int > q ;
	for( R i = 0 ; i < 26 ; i ++ ) if( tr [0][i] ) q.push( tr [0][i] ) ;
	
	while( !q.empty() ){
		int x = q.front() ; q.pop() ;
		for( R i = 0 ; i < 26 ; i ++ ){
			if( tr [x][i] ) fail [tr [x][i]] = tr [fail [x]][i] , st [tr [x][i]] |= st [tr [fail [x]][i]] , q.push( tr [x][i] ) ;
			else tr [x][i] = tr [fail [x]][i] ;
		}
	} 
}

void bfs(){
	queue< int > qaq, qwq ;
	qaq.push( 0 ) , qwq.push( 0 ) ;
	fg [0][0][0] = 1 ;
	while( !qaq.empty() ){ 
		int x = qaq.front() , sa = qwq.front() ;
		qaq.pop() , qwq.pop() ;
//		printf( "%ld\n" , x ) ;
		if( sa == ( ( 1 << n ) - 1 ) ){
			ans ++ ; int cine = ciner , top = 0 , stk [N] ; 
			memset( stk , 0 , sizeof( stk ) ) ;
			while( cine ){
				stk [++ top] = chr [cine] ;
				cine = fa [cine] ;
			} if( top == l ) { for( R i = top ; i ; i -- ) putchar( stk [i] + 'a' ) ; puts( "" ) ; }
		}
		
		for( R i = 0 ; i < 26 ; i ++ ){
			if( fg [x][tr [x][i]][sa | st [tr [x][i]]] <= 10 ){
				fg [x][tr [x][i]][sa | st [tr [x][i]]] ++ ;
				chr [++ alls] = i ;
				fa [alls] = ciner ;
				qaq.push( tr [x][i] ) ;
				qwq.push( sa | st [tr [x][i]] ) ;
			}
		}
		ciner ++ ;
	}
}

void sc(){
	l = read() , n = read() ;
	for( R i = 1 ; i <= n ; i ++ ) 
		scanf( "%s" , m [i].s + 1 ) , ins( m [i].s , i ) ;
}

void work(){
	build() ;
	f [0][0][0] = 1 ;
	for( R i = 0 ; i < l ; i ++ )
		for( R j = 0 ; j < ( 1 << n ) ; j ++ )
			for( R k = 0 ; k <= cnt ; k ++ )
				for( R p = 0 ; p < 26 ; p ++ )
				f [i + 1][j | st [tr [k][p]]][tr [k][p]] += f [i][j][k] ;
	for( R i = 0 ; i <= cnt ; i ++ ) anss += f [l][( 1 << n ) - 1][i] ;
	printf( "%lld\n" , anss ) ;		
	if( anss <= 42 ) bfs() ;                        	
}

signed main(){	
	sc() ;
	work() ;
	return 0 ;
}

题解做法

由于方案数小于等于\(42\),所以必定不存在一个位置,使得这个位置可以填\(26\)个字母,如果有,那么方案数必定大于\(52\)
最简单证明:

如果只有一个串 ,abcd?,?可以随便填,那么就有\(26\)种方案,而把它变成?abcd,就又有\(26\)种方案。
如果多个串之间有一个地方可以随便填abcd?efg,那么交换这两个串必定也是合法方案efg?abcd

所以,需要输出方案的字符串,必定是紧密结合的。
所以只需要预处理出来每两个字符串连接需要多少长度,然后直接\(O(n!)\)枚举就行了。
注意一下,需要把拼接之后长度大于\(L\)的去掉。

code
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <queue>
#include <iostream>
#include <bitset>
#define mp make_pair
#define R register int
#define int long 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 = 1e2 + 10 ;
const int E = 1e1 + 2 ;
const int M = N * ( 1 << E ) ; 

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 ; 
}

inline void wap( int &a , int &b ){ a = a ^ b , b = a ^ b , a = a ^ b ; }
inline int mins( int a , int b ){ int zt = b - a ; return a + ( zt & ( zt >> 31 ) ) ; }
inline int maxs( int a , int b ){ int zt = b - a ; return b - ( zt & ( zt >> 31 ) ) ; }
inline int abss( int a ){ int zt = a >> 31 ; return ( a + zt ) ^ zt ; }
int n ,	l , cnt , anss , tr [M][27] , st [M] , fail [M] , ans , f [10 * E][1 << E][N] ;
int link [50][50] , totl , tops ; // link [i][j]代表i的后缀和j的前缀的匹配长度
char answer [50][50] ;

struct P{ char s [N] ; } m [50] ;

inline void ins( char s [] , int ix ){
	int len = strlen( s + 1 ) , x = 0 ;
	for( R i = 1 ; i <= len ; i ++ ){
		int ch = s [i] - 'a' ;
		if( !tr [x][ch] ) tr [x][ch] = ++ cnt ;
		x = tr [x][ch] ;
	} st [x] |= ( 1 << ( ix - 1 ) ) ;
}

void build(){
	queue< int > q ;
	for( R i = 0 ; i < 26 ; i ++ ) if( tr [0][i] ) q.push( tr [0][i] ) ;
	
	while( !q.empty() ){
		int x = q.front() ; q.pop() ;
		for( R i = 0 ; i < 26 ; i ++ ){
			if( tr [x][i] ) fail [tr [x][i]] = tr [fail [x]][i] , st [tr [x][i]] |= st [tr [fail [x]][i]] , q.push( tr [x][i] ) ;
			else tr [x][i] = tr [fail [x]][i] ;
		}
	}
}

void sc(){
	l = read() , n = read() ;
	for( R i = 1 ; i <= n ; i ++ ) 
		scanf( "%s" , m [i].s + 1 ) , ins( m [i].s , i ) , totl += strlen( m [i].s + 1 ) ;
}

void pre(){
	for( R i = 1 ; i <= n ; i ++ ){
		int leni = strlen( m [i].s + 1 ) ;
		for( R j = 1 ; j <= n ; j ++ ){
			if( i == j ) continue ;
			int fl = 0 , lenj = strlen( m [j].s + 1 ) ;
			for( R k = 1 ; k <= leni ; k ++ ){
				fl = 0 ;
				for( R l = lenj - k + 1 , p = 1 ; l <= lenj ; l ++ , p ++ )
					if( m [i].s [p] != m [j].s [l] ) { fl = 1 ; break ; }
				 if( !fl ) link [j][i] = k ; 	
			}  
		}
	}
}

inline void cpys( int p [] ){
	int now = 0 ;
	for( R i = 1 ; i <= n ; i ++ ){
		int lle = strlen( m [p [i]].s + 1 ) ;
		for( R j = link [p [i - 1]][p [i]] + 1 ; j <= lle ; j ++ )
			answer [tops][++ now] = m [p [i]].s [j] ;
		answer [tops][now + 1] = '\0' ;
	}
}

void print(){
	int P [50] ;
	for( R i = 0 ; i <= n ; i ++ ) P [i] = i ; 
	do{
		int ansl = totl ;  
		for( R i = 1 ; i <= n ; i ++ )
			ansl -= link [P [i]][P [i + 1]] ;
		if( ansl == l ) tops ++ , cpys( P ) ; 	
	} while( next_permutation( P + 1 , P + 1 + n ) ) ;
	
	for( R i = 1 ; i <= tops ; i ++ ){
		for( R j = i + 1 ; j <= tops ; j ++ ){
		//	printf( "%s %s %d\n" , answer [i] + 1 , answer [j] + 1 , strcmp( answer [i] , answer [j] ) ) ;
			if( strcmp( answer [i] + 1 , answer [j] + 1 ) > 0 )
			swap( answer [i] , answer [j] ) ;
		}
	}
	
	for( R i = 1 ; i <= tops ; i ++ ){
	//	for( R j = 1 ; j <= l + 1 ; j ++ )
	//	printf( "%c" , answer [i][j] ) ;
		printf( "%s" , answer [i] + 1 ) ;
		puts( "" ) ;
	}
}

void work(){
	build() ;
	f [0][0][0] = 1 ;
	for( R i = 0 ; i < l ; i ++ )
		for( R j = 0 ; j < ( 1 << n ) ; j ++ )
			for( R k = 0 ; k <= cnt ; k ++ )
				for( R p = 0 ; p < 26 ; p ++ )
				f [i + 1][j | st [tr [k][p]]][tr [k][p]] += f [i][j][k] ;
	for( R i = 0 ; i <= cnt ; i ++ ) anss += f [l][( 1 << n ) - 1][i] ;
	printf( "%lld\n" , anss ) ;		
	if( anss <= 42 ) pre() , print() ;                  	
}

signed main(){	
	sc() ;
	work() ;
	return 0 ;
}
posted @ 2021-06-30 21:39  Soresen  阅读(54)  评论(1)    收藏  举报