lightoj 1268 Unlucky Strings (矩阵快速幂+kmp DFA 好题)

77 篇文章 8 订阅
70 篇文章 0 订阅
Unlucky Strings
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

 


Problem Setter: Jane Alam Jan

题目链接: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);
    }
}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值