Time Limit: 2 second(s) | Memory Limit: 32 MB |
Mr. 'Jotishi' is a superstitious man. Before doing anythinghe usually draws some strange figures, and decides what to do next.
One day he declared that the names that contain a string Sas substring is unlucky. For example, let S be 'ab', then 'abc','cabe', 'pqqab', 'ab' etc are unlucky but 'ba', 'baa' etcare not.
So, he gives you the string S and asks you to findthe number of names of length n, which are lucky, that means you have tofind the number of strings that don't contain S as substring.
Input
Input starts with an integer T (≤ 100),denoting the number of test cases.
Each case starts with a line containing an integer n (1≤ n ≤ 109). The next line contains the allowedcharacters for a name. This non-empty line contains lowercase characters onlyand in ascending order. The next line contains a string S (1 ≤length(S) ≤ 50), and S contains characters from the allowedcharacters only.
Output
For each case, print the case number and the total number ofnames that don't contain S as substring. As the number can be verylarge, print the number modulo 232.
Sample Input | Output for Sample Input |
3 3 ab ab 4 acd ca 5 ab aaa | Case 1: 4 Case 2: 55 Case 3: 24 |
题目链接:http://lightoj.com/volume_showproblem.php?problem=1268
题目大意:给一个字符集和一个字符串str,再给一个数字n,求用字符集中的字符构造出长度为n的不包含str这个子串的字符串的个数
题目分析:首先看到n的范围,又要构造字符串,容易想到用矩阵来处理,在离散数学中,一个初始邻接矩阵的n次方代表了任意两点间通过n条路径连接的方案数,这里初始矩阵的n次方就代表从字符i到字符j经过n个字符所构造出的字符串的种类数即为cnt[i][j]
显然答案就是Σ(j = 0~len-1) cnt[0][j],取模的话因为对2^32次方取模直接unsigned自然溢出即可,所以现在主要问题就是如何构造初始矩阵,其实原理就类似确定有穷自动机(DFA),一个DFA是一个五元组M=(K, ∑,f,S,Z):
1)K是一个有穷集,他的每个元素称为一种状态。
2)∑是一个有穷字母表,他的每个元素称为一个输入符号,所以∑称为输入符号表。
3)f是转换函数,例如f(Ki, c) = Kj这就意味着,当前状态为Ki,输入字符c后,将转换到下一状态Kj,我们把Kj称为Ki的一个后继状态。
4)S属于K,是初始状态。
5)Z属于K,是一个终态,终态也称为可接受状态或结束状态。
举个例子比如给定的字符集是{a,c,d},给定的字符串是cad,对应于一个DFA
S为空,Z为cad,∑为{a,c,d},K和f就不一一列举了,直接看矩阵
0 1 2
0 a,d c 空
1 cd cc ca
2 caa cac cad
行i表示当前已经匹配了i个字符,列j表示加上字符ch[j]后状态变为匹配了j个字符,因为要保证不能匹配到字符串cad,因此[2, 2]的位置要置成空,这里构造的方法可以利用kmp的next,kmp的next数组表示最长的前后缀匹配长度,利用这个性质可以很方便的构造出初始矩阵,枚举插入字符,再枚举状态,首先若当前位置匹配,比如空状态遇到字符c,c状态遇到字符a,则对应状态直接加1,否则通过next数组一直往前找匹配的点,若找到匹配点则对应状态加1,找不到的话相当于为空,空状态也要加1,比如当前已经匹配到ca输入一个c,当前待匹配的是d,d != c,d = next[d] = 0,因为str[0] = c,相当于是匹配了两个字符的ca状态变成只匹配一个字符的cac状态,前面的ca相当于已经没用了,故mat[2][1] ++,表示有一种匹配两个字符状态到匹配一个字符状态的方案,再比如ca遇到a,d!=a,d = next[d] = 0,str[0] != a,表示有一种匹配两个字符状态到空状态的方案,故mat[2][0] ++
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int const MAX = 55;
int n, len;
char str[MAX], ch[MAX];
int nxt[MAX];
struct Matrix {
int n;
unsigned mat[MAX][MAX];
Matrix(int nn) {
n = nn;
memset(mat, 0, sizeof(mat));
}
};
Matrix mul(Matrix a, Matrix b) {
Matrix ans = Matrix(len);
for(int i = 0; i < len; i ++) {
for(int j = 0; j < len; j ++) {
for(int k = 0; k < len; k ++) {
ans.mat[i][j] += a.mat[i][k] * b.mat[k][j];
}
}
}
return ans;
}
Matrix qpow(Matrix a, int x) {
Matrix ans = Matrix(len);
for(int i = 0; i < len; i ++) {
ans.mat[i][i] = 1;
}
while(x) {
if(x & 1) {
ans = mul(ans, a);
}
a = mul(a, a);
x >>= 1;
}
return ans;
}
void get_next() {
int i = 0, j = -1;
nxt[0] = -1;
while(str[i] != '\0') {
if(j == -1 || str[i] == str[j]) {
i ++;
j ++;
nxt[i] = j;
}
else {
j = nxt[j];
}
}
}
int main() {
int T;
scanf("%d", &T);
for(int ca = 1; ca <= T; ca ++) {
printf("Case %d: ", ca);
scanf("%d %s %s", &n, ch, str);
len = strlen(str);
get_next();
Matrix ans = Matrix(0);
for(int j = 0; j < (int) strlen(ch); j ++) {
for(int i = 0; i < len; i ++) {
int pos = i;
if(str[pos] == ch[j]) {
ans.mat[i][pos + 1] ++;
continue;
}
while(pos && str[pos] != ch[j]) {
pos = nxt[pos];
}
if(str[pos] == ch[j]) {
pos ++;
}
ans.mat[i][pos] ++;
}
}
ans = qpow(ans, n);
unsigned res = 0;
for(int i = 0; i < len; i ++) {
res += ans.mat[0][i];
}
printf("%u\n", res);
}
}