KMP与AC自动机 和 DP的结合

设计密码
你现在需要设计一个密码 S , S S,S SS 需要满足:

S 的长度是 N;
S 只包含小写英文字母;
S 不包含子串 T;

例如: a b c abc abc a b c d e abcde abcde a b c d e abcde abcde 的子串, a b d abd abd 不是 a b c d e abcde abcde 的子串。

请问共有多少种不同的密码满足要求?

(由于答案会非常大,请输出答案模 1 e 9 + 7 1e9+7 1e9+7 的余数。)

输入格式
第一行输入整数 N N N,表示密码的长度。

第二行输入字符串 T , T T,T TT中只包含小写字母。

输出格式
输出一个正整数,表示总方案数模 1 e 9 + 7 1e9+7 1e9+7 后的结果。

数据范围
1 ≤ N ≤ 50 1≤N≤50 1N50,
1 ≤ ∣ T ∣ ≤ N 1≤|T|≤N 1TN ∣ T ∣ |T| T T T T的长度。


本题是DP与KMP的结合,具体状态设计是:
f [ i ] [ j ] : 构 造 了 前 i 个 字 母 , 且 当 前 字 母 对 串 T 的 状 态 是 j , ( 这 个 状 态 指 的 是 T 的 K M P 的 n e x t 数 组 ) f[i][j]:构造了前i个字母,且当前字母对串T的状态是j,\\(这个状态指的是T的KMP的next数组) f[i][j]:iTjTKMPnext

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 55, mod = 1e9 + 7;

int n, m;
int f[N][N];
//f[i][j]表示前i个字母且对于串T的状态是j的所有方案
char T[N];
int ne[N];

int main()
{
	cin >> n >> T + 1;
	m = strlen(T + 1);
	
	//求T的next数组 
	for(int i = 2, j = 0; i <= m; i++)
	{
		while(j && T[i] != T[j + 1])	j = ne[j];
		if(T[i] == T[j + 1])	j++;
		ne[i] = j;
	}
	
	f[0][0] = 1; //长度为0时状态为0只有一种方案
	for(int i = 0; i <= n; i++) //枚举长度 
		for(int j = 0; j <= m; j++)//枚举状态 
			for(char k = 'a'; k <= 'z'; k++) //枚举下一位的字母 
			{
				int u = j;
				while(u && k != T[u + 1])	u = ne[u]; 
				//求在next数组中转移后的状态
				if(k == T[u + 1])	u++;
				if(u < m)	f[i + 1][u] = (f[i + 1][u] + f[i][j]) % mod;
				//如果状态合法才进行dp的转移,合法指当前匹配的字母个数u小于m
			}
			
	int res = 0;
	for(int i = 0; i < m; i++)	res = (res + f[n][i]) % mod;
	
	cout << res << endl;
	return 0;
}



文本生成器

题目描述
JSOI 交给队员 ZYX 一个任务,编制一个称之为“文本生成器”的电脑软件:该软件的使用者是一些低幼人群,他们现在使用的是 GW 文本生成器 v6 版。

该软件可以随机生成一些文章——总是生成一篇长度固定且完全随机的文章。 也就是说,生成的文章中每个字符都是完全随机的。如果一篇文章中至少包含使用者们了解的一个单词,那么我们说这篇文章是可读的(我们称文章 s s s 包含单词 t t t,当且仅当单词 t t t 是文章 s s s 的子串)。但是,即使按照这样的标准,使用者现在使用的 GW 文本生成器 v6 版所生成的文章也是几乎完全不可读的。ZYX 需要指出 GW 文本生成器 v6 生成的所有文本中,可读文本的数量,以便能够成功获得 v7 更新版。你能帮助他吗?

答案对 1 0 4 + 7 10^4 + 7 104+7 取模。

输入格式
第一行有两个整数,分别表示使用者了解的单词总数 n n n 和生成的文章长度 m m m
接下来 n n n 行,每行一个字符串 s i s_i si ,表示一个使用者了解的单词。

输出格式
输出一行一个整数表示答案对 1 0 4 + 7 10^4 + 7 104+7 取模的结果。


上一题是构造长度为 n n n的且不包含给定子串的字符串的个数。
本题是构造长度为 n n n的且不包含多个给定子串的字符串的个数

本题是上一题的AC自动机版本,状态设计也和上一题类似:
f [ i ] [ j ] : 长 度 是 i , 在 a c 自 动 机 上 的 状 态 是 j 的 所 有 不 包 含 给 定 单 词 的 字 符 串 的 个 数 f[i][j]:长度是i,在ac自动机上的状态是j的所有不包含给定单词的字符串的个数 f[i][j]iacj

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define INF 0x3f3f3f3f

using namespace std;

typedef long long LL;

const int N = 110, M = 6010, Mod = 10007;

int n, m, f[N][M]; //f[i][j]:长度是i,在ac自动机上的状态是j的所有不包含给定单词的字符串的个数
int idx, tr[M][26], ne[M];
bool have[M];
char str[N];

void insert()
{
    int p = 0;
    for(int i = 0; str[i]; i ++)
    {
        int t = str[i] - 'A';
        if(!tr[p][t])   tr[p][t] = ++ idx;
        p = tr[p][t];
    }
    have[p] = true;
}

int q[M];
void build()
{
    int hh = 0, tt = -1;
    for(int i = 0; i < 26; i ++)
        if(tr[0][i])    q[++ tt] = tr[0][i];
    
    while(hh <= tt)
    {
        int u = q[hh ++];
        for(int i = 0; i < 26; i ++)
        {
            int &p = tr[u][i];
            if(!p)  p = tr[ne[u]][i];
            else
            {
                ne[p] = tr[ne[u]][i];
                q[++ tt] = p;
                have[p] |= have[ne[p]];
            }
        }
    }
}

int main()
{
#ifdef LOCAL
    freopen("in.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif

    cin >> n >> m;
    for(int i = 0; i < n; i ++)
    {
        cin >> str;
        insert();
    }

    build();

    f[0][0] = 1;
    for(int i = 0; i < m; i ++) //枚举长度
        for(int j = 0; j <= idx; j ++) //枚举状态
            for(int k = 0; k < 26; k ++) //枚举下一位填什么
            {
                int p = tr[j][k];
                if(!have[p])    f[i + 1][p] = (f[i + 1][p] + f[i][j]) % Mod; 
                //如果状态合法才进行dp的转移,合法指没有字符串在p处结尾
            }
    int ans = 1;
    for(int i = 0; i < m; i ++) ans = (ans * 26) % Mod;
    for(int i = 0; i <= idx; i ++)  ans = (ans - f[m][i] + Mod) % Mod;

    cout << ans << endl;

    return 0;
}

u p d a t e , 2021.10.2 update,2021.10.2 update2021.10.2

最近在学矩阵快速幂,然后惊奇的发现KMP和AC自动机的DP还可以用矩阵快速幂加速。

第一题:GT考试
在这里插入图片描述
该题给出一个字符串,问所有长度为 n n n的字符串中,有多少个是不包含该字符串的。注意 n n n的范围高达 1 0 9 10^9 109!!!

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <set>
#include <map>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <algorithm>
#define INF 0x3f3f3f3f

using namespace std;

typedef long long LL;

const int N = 20;

int n, m, mod;
char str[N];
int ne[N], base[N][N], a[N][N];

void mul(int a[][N], int b[][N]) //矩阵乘法
{
    static int c[N][N];
    memset(c, 0, sizeof c);

    for(int i = 0; i < m; i ++)
        for(int j = 0; j < m; j ++)
            for(int k = 0; k < m; k ++)
                c[i][j] = (c[i][j] + a[i][k] * b[k][j]) % mod;
    
    memcpy(a, c, sizeof c);
}

int qmi(int k) //矩阵快速幂
{
    for(int i = 0; i < m; i ++) //求出来base矩阵
        for(char ch = '0'; ch <= '9'; ch ++)
        {
            int j = i;
            while(j && str[j + 1] != ch)    j = ne[j];
            if(str[j + 1] == ch)    j ++;
            if(j < m)   base[i][j] ++;
        }
    
    a[0][0] = 1; //答案矩阵,初始化
    while(k)
    {
        if(k & 1)   mul(a, base);
        mul(base, base);
        k >>= 1;
    }

    int res = 0;//统计结果
    for(int i = 0; i < m; i ++) res = (res + a[0][i]) % mod;
    return res;
}

int main()
{
#ifdef LOCAL
    freopen("in.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif

    cin >> n >> m >> mod >> str + 1;

    for(int i = 2, j = 0; i <= m; i ++) //KMP
    {
        while(j && str[j + 1] != str[i])    j = ne[j];
        if(str[j + 1] == str[i])    j ++;
        ne[i] = j;
    }

    cout << qmi(n) << endl;

    return 0;
}

第二题:POJ2778 DNA Sequence

题目大意:
给定 m m m n n n,分别代表接下来会给你 m m m个病毒串(每个串的长度不会超过 10 10 10),问你长度为n的不包含病毒的串有多少种。
( 0 < = m < = 10 , 1 < = n < = 2 e 9 ) (0<=m<=10,1<=n<=2e9) 0<=m<=101<=n<=2e9

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <set>
#include <map>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <algorithm>
#define INF 0x3f3f3f3f

using namespace std;

typedef long long LL;

const int N = 10, M = 110, MOD = 100000;

int n, m;
int idx, tr[M][4], ne[M], q[M];
bool have[M];
char str[N];

int get(char ch)
{
    if(ch == 'A')   return 0;
    if(ch == 'T')   return 1;
    if(ch == 'G')   return 2;
    return 3;
}

void insert()
{
    int p = 0;
    for(int i = 0; str[i]; i ++)
    {
        int u = get(str[i]);
        if(!tr[p][u])   tr[p][u] = ++ idx;
        p = tr[p][u];
    }
    have[p] = true;
}

void build()
{
    int hh = 0, tt = -1;
    for(int i = 0; i < 4; i ++)
        if(tr[0][i])    q[++ tt] = tr[0][i];
    
    while(hh <= tt)
    {
        int u = q[hh ++];
        for(int i = 0; i < 4; i ++)
        {
            int &p = tr[u][i];
            if(!p)  p = tr[ne[u]][i];
            else
            {
                ne[p] = tr[ne[u]][i];
                q[++ tt] = p;
                have[p] |= have[ne[p]];
            }
        }
    }
}

void mul(int a[][M], int b[][M]) //矩阵乘法
{
    static int c[M][M];
    memset(c, 0, sizeof c);

    for(int i = 0; i <= idx; i ++)
        for(int j = 0; j <= idx; j ++)
            for(int k = 0; k <= idx; k ++)
                c[i][j] = (c[i][j] + (LL)a[i][k] * b[k][j]) % MOD;

    memcpy(a, c, sizeof c);
}

int base[M][M], a[M][M];

int qmi(int k) //矩阵快速幂
{
    for(int i = 0; i <= idx; i ++) //求base矩阵
        for(int k = 0; k < 4; k ++)
        {
            int j = tr[i][k];
            if(!have[i] && !have[j])    base[i][j] ++;
            //只有当i点和j点都是安全节点时才可以转移
        }
    
    //for(int i = 0; i <= idx; i ++)  a[i][i] = 1;
    a[0][0] = 1; //答案矩阵初始化
    while(k)
    {
        if(k & 1)   mul(a, base);
        mul(base, base);
        k >>= 1;
    }

    int res = 0; //统计结果
    for(int i = 0; i <= idx; i ++)  res = (res + a[0][i]) % MOD;
    return res;
}

int main()
{
#ifdef LOCAL
    freopen("in.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif

    cin >> m >> n;
    for(int i = 0; i < m; i ++)
    {
        cin >> str;
        insert();
    }
    build();
	//以上是构建AC自动机的过程
    cout << qmi(n) << endl;

    return 0;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值