【HDU】2825 Wireless Password AC自动机+DP

Wireless Password

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 4100    Accepted Submission(s): 1231


Problem Description
Liyuan lives in a old apartment. One day, he suddenly found that there was a wireless network in the building. Liyuan did not know the password of the network, but he got some important information from his neighbor. He knew the password consists only of lowercase letters 'a'-'z', and he knew the length of the password. Furthermore, he got a magic word set, and his neighbor told him that the password included at least k words of the magic word set (the k words in the password possibly overlapping).

For instance, say that you know that the password is 3 characters long, and the magic word set includes 'she' and 'he'. Then the possible password is only 'she'.

Liyuan wants to know whether the information is enough to reduce the number of possible passwords. To answer this, please help him write a program that determines the number of possible passwords.
 

Input
There will be several data sets. Each data set will begin with a line with three integers n m k. n is the length of the password (1<=n<=25), m is the number of the words in the magic word set(0<=m<=10), and the number k denotes that the password included at least k words of the magic set. This is followed by m lines, each containing a word of the magic set, each word consists of between 1 and 10 lowercase letters 'a'-'z'. End of input will be marked by a line with n=0 m=0 k=0, which should not be processed.
 

Output
For each test case, please output the number of possible passwords MOD 20090717.
 

Sample Input
 
   
10 2 2 hello world 4 1 1 icpc 10 0 0 0 0 0
 

Sample Output
 
   
2 1 14195065
 

Source
2009 Multi-University Training Contest 1 - Host by TJU

传送门:【HDU】2825 Wireless Password

这题时隔多日终于想出来了,先前一直没有思路,只知道需要状态压缩。今日床上思考着突然觉得差不多能写出来了,就赶紧起床去写掉,,一开始C++TLE,一弃疗投到G++,800ms+险过。不过终于填掉这个坑了。

题目大意:
大概就是说,有个人想去蹭邻居的WIFI,但不知道密码,现在他知道这个密码的长度n(1 <= n <= 25 ),以及m(0 <= m <= 10 )个单词,密码可能包含其中某些单词(长度1~10且均为小写字母),并且保证单词中至少有k(0 <= k <= m)个单词是在知道的单词中的。单词之间可能有部分重叠。
现在,你需要帮他计算出有多少种可能的密码。当然答案可能很多,只需要输出结果 MOD 20090717即可。

题目分析:
AC自动机+状态压缩DP。
因为单词数不超过10个,那我们用二进制表示相应位置对应的单词的有无即可。1代表有,0代表没有。
用dp[ i ][ j ][ l ]表示密码长度为i,当前状态为j,包含单词情况为l的方案数,next[ j ][ k ]表示在状态j的下一个字母是k+'a'的后继状态,end[ next[ j ][ k ] ]就表示后继状态包含单词的情况。
那么我们通过当前结点更新之后的结点:
dp[ i ][ next[ j ][ k ] ][ l | end[ next[ j ][ k ] ] ] += dp[ i ][ j ][ l ] ;
最后就是判断长度为n的所有状态上,包含单词数大于等于k的方案数有多少。
最后计数的时候,如果结点状态套在外层,单词状态套在内层,则会有很多无用之举,因此我险些超时。。最后换成单词状态在外层,结点状态在内层,效率就高了很多了。

代码如下:

#include 
#include 
#include 
using namespace std ;

#define REPF( i , a , b ) for ( int i = a ; i <= b ; ++ i )
#define REPV( i , a , b ) for ( int i = a ; i >= b ; -- i )
#define REP( i , n ) for ( int i = 0 ; i < n ; ++ i )
#define REPC( i ) for ( int i = 0 ; buf[i] ; ++ i )
#define clear( a , x , SIZE ) memset ( a , x , sizeof ( a[0] ) * ( SIZE + 1 ) )

const int MAXW = 26 ;
const int MAXN = 105 ;
const int MAXL = 1024 ;
const int MAXQ = 100000 ;
const int MOD = 20090717 ;

struct Trie {
	int next[MAXN][MAXW] ;
	int end[MAXN] ;
	int fail[MAXN] ;
	int Q[MAXQ] ;
	int head , tail ;
	int P , root ;
	int dp[26][MAXN][MAXL] ;
	
	int newnode () {
		REP ( i , MAXW )
			next[P][i] = -1 ;
		end[P] = 0 ;
		return P ++ ;
	}
	
	void init () {
		P = 0 ;
		root = newnode () ;
	}
	
	int get ( char x ) {
		return x - 'a' ;
	}
	
	void insert ( char buf[] , int idx ) {
		int now = root ;
		REPC ( i ) {
			int x = get ( buf[i] ) ;
			if ( next[now][x] == -1 )
				next[now][x] = newnode () ;
			now = next[now][x] ;
		}
		end[now] = ( 1 << idx ) ;
	}
	
	void build () {
		head = tail = 0 ;
		fail[root] = root ;
		REP ( i , MAXW ) {
			if ( next[root][i] == -1 )
				next[root][i] = root ;
			else {
				fail[next[root][i]] = root ;
				Q[tail ++] = next[root][i] ;
			}
		}
		while ( head != tail ) {
			int now = Q[head ++] ;
			REP ( i , MAXW ) {
				if ( next[now][i] == -1 )
					next[now][i] = next[fail[now]][i] ;
				else {
					fail[next[now][i]] = next[fail[now]][i] ;
					if ( end[fail[next[now][i]]] )
						end[next[now][i]] |= end[fail[next[now][i]]] ;
					Q[tail ++] = next[now][i] ;
				}
			}
		}
	}
	
	int solve ( int n , int m , int least ) {
		int L = 1 << m ;
		REP ( i , n + 1 )
			REP ( j , P )
				clear ( dp[i][j] , 0 , L ) ;
		dp[0][0][0] = 1 ;
		REP ( i , n )
			REP ( j , P )
				REP ( l , L )
					if ( dp[i][j][l] )
						REP ( k , MAXW ) {
							int Next = next[j][k] , o = ( l | end[Next] ) ;
							dp[i + 1][Next][o] += dp[i][j][l] ;
							if ( dp[i + 1][Next][o] >= MOD )
								dp[i + 1][Next][o] -= MOD ;
						}
		int ans = 0 ;
		REP ( l , L ) {
			int cnt = 0 , ll = l ;
			while ( ll ) {
				if ( ll & 1 )
					cnt ++ ;
				ll >>= 1 ;
			}
			if ( cnt >= least )
				REP ( j , P ) {
					ans += dp[n][j][l] ;
					if ( ans >= MOD )
						ans -= MOD ;
				}
		}
		return ans ;
	}
} ;

Trie ac ;
char buf[MAXN] ;

void work () {
	int n , m , least ;
	while ( ~scanf ( "%d%d%d" , &n , &m , &least ) && ( n || m || least ) ) {
		ac.init () ;
		REP ( i , m ) {
			scanf ( "%s" , buf ) ;
			ac.insert ( buf , i ) ;
		}
		ac.build () ;
		printf ( "%d\n" , ac.solve ( n , m , least ) ) ;
	}
}

int main () {
	work () ;
	return 0 ;
}


你可能感兴趣的:(DP,AC自动机【Trie图】)