AC自动机求定长不含关键词的串的种数

题目大意

给定 m m m个字符串,求长度为 n n n的、给定的 m m m个字符串不是其子串的字符串的种类数量。

做法

m m m个字符串建立一个AC自动机。此时我们考虑这个AC自动机中fail指针的含义,就是失配后通过特定字母可以到达的结点位置。

那么,整个AC自动机上,基于trie树的节点之间的父子关系+fail指针构成了节点与节点之间的移动路线。

我们从AC自动机的 0 0 0号点(trie树的根)出发,每走一步就相当于增加了一个字母,那么最后就是看从 0 0 0开始,走 n n n步到达所有可以到达的点的方案数。

所谓可以到达的点,就是可以走到的节点。换言之,如果走到这个节点凑出了 m m m个字符串之一,那么就是不合法节点,即不可到达的点。反过来就是可以到达的。

什么样的点是不可到达的?如上所说,凑出了 m m m个字符串之一,即关键词的最后一个字母所代表的节点,一定是不可到达的。
根据fail指针的定义,我们也可以发现,如果一个节点可以通过fail指针失配到一个不可到达的节点,那么这个节点也一定是不可到达的

统计出不可到达的点之后,我们来看每一个点走一步之后可以到达哪些位置。

一般这种题目AC自动机的节点数量会非常少,所以我们可以用二维数组 c n t cnt cnt存储节点与节点之间走一步能否达到的关系。我们遍历每一个可以达到的节点 u u u,如果他可以去另外一个可以达到的节点 v v v,那么我们令 c n t [ u ] [ v ] + + cnt[u][v]++ cnt[u][v]++

全部统计完后,我们就获得了走一步之后,第 i i i个节点能否到第 j j j个节点了。将其封装成一个矩阵 A A A。如果我们要走 n n n步,那么就求出矩阵 A A A n n n次幂,然后求最终结果矩阵 B B B中对于所有 i ∈ [ 0 , t o t ] i∈[0,tot] i[0,tot](tot是AC自动机中的点数)中 B [ 0 ] [ i ] B[0][i] B[0][i]的和。这个和就是题目的答案,即长度为 n n n的字符串中不包含 m m m个关键词的种类个数。

即:假设

A = [ a 0 , 0 a 0 , 1 ⋯ a 0 , t o t a 1 , 0 a 1 , 1 ⋯ a 1 , t o t ⋮ ⋮ ⋱ ⋮ a t o t , 0 a t o t , 1 ⋯ a t o t , t o t ] A= \left[ \begin{matrix} a_{0,0} & a_{0,1} & \cdots & a_{0,tot} \\ a_{1,0} & a_{1,1} & \cdots & a_{1,tot} \\ \vdots & \vdots & \ddots & \vdots \\ a_{tot,0} & a_{tot,1} & \cdots & a_{tot,tot} \end{matrix} \right] A=a0,0a1,0atot,0a0,1a1,1atot,1a0,tota1,totatot,tot

那么答案 a n s = a 0 , 0 + a 0 , 1 + a 0 , 2 + ⋯ + a 0 , t o t ans=a_{0,0}+a_{0,1}+a_{0,2}+\cdots +a_{0,tot} ans=a0,0+a0,1+a0,2++a0,tot,表示走 n n n步后从 0 0 0点走到各个点的方案数之和。

一般情况下 n n n会非常大,大约是 2 31 2^{31} 231的数量级,所以必须要用矩阵快速幂来加速矩阵运算。

POJ 2778 - DNA Sequence

和上面的题意完全一致。(或者说上面的题意是照着这个题目写的)
题目链接
传递的字母只有4个。

Code:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<queue>
using namespace std;
typedef long long LL;
const LL p = 100000;
const int N = 15;
const int matSize = 150;

int bad[N * 15];

int getID(char c) {
	if (c == 'A') return 0;
	else if (c == 'T') return 1;
	else if (c == 'C') return 2;
	else return 3; 
}

int tot;
int tr[N * 15][5], fail[N * 15], idx[N * 15], val[N * 15], cnt[N];

struct AC_Automaton {
	void init() {
		tot = 0;
		memset(bad, 0, sizeof(bad));
	}
	 
	void insert(char *s, int id) {
		int u = 0;
		for (int i = 1; s[i]; ++i) {
			if (!tr[u][getID(s[i])]) tr[u][getID(s[i])] = ++tot;
			u = tr[u][getID(s[i])];
		}
		idx[u] = id; bad[u] = 1;
	}
	
	queue<int> q;
	
	void build() {
		for (int i = 0; i < 4; ++i) {
			if (tr[0][i]) q.push(tr[0][i]);
		}
		while (q.size()) {
			int u = q.front();
			q.pop();
			for (int i = 0; i < 4; ++i) {
				if (tr[u][i]) {
					fail[tr[u][i]] = tr[fail[u]][i];
					bad[u] |= bad[fail[u]];
					q.push(tr[u][i]);
				}
				else tr[u][i] = tr[fail[u]][i];
			}
		}
	}
	
	int query(char *t) {
		int u = 0, res = 0;
		for (int i = 1; t[i]; ++i) {
			u = tr[u][getID(t[i])];
			for (int j = u; j; j = fail[j]) val[j]++;
		}
		for (int i = 0; i <= tot; ++i) {
			if (idx[i]) {
				res = max(res, val[i]);
				cnt[idx[i]] = val[i];
			}
		}
		return res;
	}
}ac;

struct Matrix {
	int matA[150][150];
	inline Matrix() { memset(matA, 0, sizeof(matA)); }
	inline Matrix operator - (const Matrix &MAT_T) const {
		Matrix MAT_RES;
		for (int i = 0; i < matSize; ++i) for (int j = 0; j < matSize; ++j)
			MAT_RES.matA[i][j] = (matA[i][j] - MAT_T.matA[i][j]) % p;
		return MAT_RES;
	}
	inline Matrix operator + (const Matrix &MAT_T) const {
		Matrix MAT_RES;
		for (int i = 0; i < matSize; ++i) for (int j = 0; j < matSize; ++j)
			MAT_RES.matA[i][j] = (matA[i][j] + MAT_T.matA[i][j]) % p;
		return MAT_RES;
	}
	inline Matrix operator * (const Matrix &MAT_T) const {
		Matrix MAT_RES;
		for (int i = 0; i <= tot; ++i) {
			for (int j = 0; j <= tot; ++j) {
				MAT_RES.matA[i][j] = 0;
				LL res = 0;
				for (int k = 0; k <= tot; ++k) {
					res += ((LL)matA[i][k] * MAT_T.matA[k][j]);
				}
				MAT_RES.matA[i][j] = res % p;
			}
		}
		return MAT_RES;
	}
	inline Matrix operator ^ (LL MAT_X) const {
		Matrix MAT_RES, MAT_BAS;
		for (int i = 0; i < matSize; ++i) MAT_RES.matA[i][i] = 1;
		for (int i = 0; i < matSize; ++i) for (int j = 0; j < matSize; ++j)
			MAT_BAS.matA[i][j] = matA[i][j];
		while (MAT_X) {
			if (MAT_X&1ll)MAT_RES=MAT_RES*MAT_BAS; 
			MAT_BAS=MAT_BAS*MAT_BAS; MAT_X>>=1;
		}
		return MAT_RES;
	}
};

int n, m;
char s[15];

Matrix Power(Matrix &A, int a) {
	Matrix ans;
	for (int i = 0; i <= tot; ++i) {
		ans.matA[i][i] = 1;
	} 
	while (a) {
		if (a & 1) ans = ans * A;
		A = A * A;
		a >>= 1;
	}
	return ans;
}

int main() {
	scanf("%d%d", &m, &n);
	ac.init();
	for (int i = 1; i <= m; ++i) {
		scanf("%s", s + 1);
		ac.insert(s, i);
	}
	ac.build();
	Matrix A;
	for (int i = 0; i <= tot; ++i) {
		if (bad[i]) continue;
		for (int j = 0; j <= 3; ++j) {
			int nxt = tr[i][j];
			if (!bad[nxt]) ++A.matA[i][nxt];
		}
	}
	A = Power(A, n);
	LL ans = 0;
	for (int i = 0; i <= tot; ++i) {
		ans = (ans + A.matA[0][i]);
	} 
	printf("%d", ans % p);
	return 0;
}

HDU 2243 - 考试路茫茫——单词情结

这个题和上一个题刚好反过来,是求长度不超过 n n n的所有串里包含 m m m个关键词中任意一个或多个的串的种类数量。

解法

既然是反过来的题目,那我们也反过来解。我们会求不包含这些词的串的数量,那我们就求所有可能的串的种数-不包含这些词的串的数量,就是我们要求的答案。

所有可能的串的种数非常好求。长度为 1 1 1的串的数量是 26 26 26,长度为 2 2 2的串的数量是 2 6 2 26^2 262,长度为 3 3 3的串的数量是 2 6 3 26^3 263……

所以所有可能的串的总数 S = 2 6 1 + 2 6 2 + ⋯ + 2 6 n S=26^1+26^2+\cdots+26^n S=261+262++26n

但是这道题目 n n n非常大,是 2 31 2^{31} 231级别的,所以 O ( n ) O(n) O(n)是不行的。我们考虑用矩阵来求。我们可以列出以下矩阵乘法式:

[ 26 1 0 1 ] n [ 1 1 ] = [ S + 1 1 ] \left[ \begin{matrix} 26 & 1 \\ 0 & 1 \end{matrix} \right]^n \left[ \begin{matrix} 1 \\ 1 \end{matrix} \right] = \left[ \begin{matrix} S+1 \\ 1 \end{matrix} \right] [26011]n[11]=[S+11]

这样的话利用矩阵快速幂就可以在 O ( log ⁡ n ) O(\log n) O(logn)的时间复杂度里求出 S S S

现在来求长度不超过 n n n的不包含 m m m个关键词的串的种数。我们现在会求长度固定的串的种数。假设我们获得的移动一位的矩阵是 A A A。如果定长的话,我们令 B = A n B=A^n B=An,如果
B = [ b 0 , 0 b 0 , 1 ⋯ b 0 , t o t b 1 , 0 b 1 , 1 ⋯ b 1 , t o t ⋮ ⋮ ⋱ ⋮ b t o t , 0 b t o t , 1 ⋯ b t o t , t o t ] B= \left[ \begin{matrix} b_{0,0} & b_{0,1} & \cdots & b_{0,tot} \\ b_{1,0} & b_{1,1} & \cdots & b_{1,tot} \\ \vdots & \vdots & \ddots & \vdots \\ b_{tot,0} & b_{tot,1} & \cdots & b_{tot,tot} \end{matrix} \right] B=b0,0b1,0btot,0b0,1b1,1btot,1b0,totb1,totbtot,tot

那么对于长度为 n n n的串的种类数量 a n s n = b 0 , 0 + b 0 , 1 + ⋯ + b 0 , t o t ans_n=b_{0,0}+b_{0,1}+\cdots+b_{0,tot} ansn=b0,0+b0,1++b0,tot

我们现在要求的是 a n s 1 + a n s 2 + ⋯ + a n s n ans_1+ans_2+\cdots+ans_n ans1+ans2++ansn

我们不能 O ( n ) O(n) O(n)求,那么就需要借助矩阵来求。这里有一个小技巧,我们只需要把整个矩阵尺寸放大一圈,外围都用 0 0 0 1 1 1填充,像这个样子——

A = [ a 0 , 0 a 0 , 1 ⋯ a 0 , t o t 1 a 1 , 0 a 1 , 1 ⋯ a 1 , t o t 1 ⋮ ⋮ ⋱ ⋮ ⋮ a t o t , 0 a t o t , 1 ⋯ a t o t , t o t 1 0 0 ⋯ 0 1 ] A= \left[ \begin{matrix} a_{0,0} & a_{0,1} & \cdots & a_{0,tot} & 1 \\ a_{1,0} & a_{1,1} & \cdots & a_{1,tot} & 1 \\ \vdots & \vdots & \ddots & \vdots & \vdots \\ a_{tot,0} & a_{tot,1} & \cdots & a_{tot,tot} & 1 \\ 0 & 0 & \cdots & 0 & 1 \end{matrix} \right] A=a0,0a1,0atot,00a0,1a1,1atot,10a0,tota1,totatot,tot01111

我们试着来给他乘方,令 A i A^i Ai的右上角元素为 x i x_i xi,我们惊奇地发现,有

x i = 1 + a n s 1 + ⋯ + a n s i − 1 x_i=1+ans_1+\cdots + ans_{i-1} xi=1+ans1++ansi1

那么有

x n = 1 + a n s 1 + ⋯ + a n s n − 1 x_n=1+ans_1+\cdots+ans_{n-1} xn=1+ans1++ansn1

如果 B = A n B=A^n B=An,那么我们可以通过 B B B求得 a n s n ans_n ansn

a n s n = b 0 , 0 + b 0 , 1 + ⋯ + b 0 , t o t ans_n=b_{0,0}+b_{0,1}+\cdots+b_{0,tot} ansn=b0,0+b0,1++b0,tot

x n = b 0 , t o t + 1 x_n=b_{0,tot+1} xn=b0,tot+1 a n s 0 = 1 ans_0=1 ans0=1,所以我们长度不超过 n n n的所有不包含给出的字符串的串的种数 a n s ans ans

a n s = b 0 , 0 + b 0 , 1 + ⋯ + b 0 , t o t + 1 − 1 ans=b_{0,0}+b_{0,1}+\cdots+b_{0,tot+1}-1 ans=b0,0+b0,1++b0,tot+11

那么包含给出的 m m m个字符串中的一个或多个的字符串的种数就是 S − a n s S-ans Sans

注意unsigned long long输出。

Code:

#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long LL;
const LL matSize = 50;
const LL N = 8;

LL n, m;
LL tot;
LL tr[N * 10][26], fail[N * 10], bad[N * 10], val[N * 10], cnt[N];

struct AC_Automaton {
	void init() {
		memset(fail, 0, sizeof(fail));
		memset(tr, 0, sizeof(tr));
		memset(val, 0, sizeof(val));
		memset(cnt, 0, sizeof(cnt));
		memset(bad, 0, sizeof(bad));
		tot = 0;
	}
	
	void insert(char *s, int id) {
		int u = 0;
		for (int i = 1; s[i]; ++i) {
			if (!tr[u][s[i] - 'a']) tr[u][s[i] - 'a'] = ++tot;
			u = tr[u][s[i] - 'a'];
		}
		bad[u] = 1;
	}
	
	queue<int> q;
	
	void build() {
		for (int i = 0; i < 26; ++i) {
			if (tr[0][i]) q.push(tr[0][i]);
		}
		while (q.size()) {
			int u = q.front();
			q.pop();
			for (int i = 0; i < 26; ++i) {
				if (tr[u][i]) {
					fail[tr[u][i]] = tr[fail[u]][i];
					bad[u] |= bad[fail[u]];
					q.push(tr[u][i]);
				}
				else tr[u][i] = tr[fail[u]][i];
			}
		}
	}
}ac;

struct Matrix
{
	LL ms = matSize;
	LL matA[matSize][matSize];
	inline Matrix() { memset(matA, 0, sizeof(matA)); }
	inline Matrix operator - (const Matrix &MAT_T) const {
		Matrix MAT_RES;
		for (int i = 0; i < ms; ++i) for (int j = 0; j < ms; ++j)
			MAT_RES.matA[i][j] = (matA[i][j] - MAT_T.matA[i][j]);
		return MAT_RES;
	}
	inline Matrix operator + (const Matrix &MAT_T) const {
		Matrix MAT_RES;
		for (int i = 0; i < ms; ++i) for (int j = 0; j < ms; ++j)
			MAT_RES.matA[i][j] = (matA[i][j] + MAT_T.matA[i][j]);
		return MAT_RES;
	}
	inline Matrix operator * (const Matrix &MAT_T) const {
		Matrix MAT_RES; LL MAT_R;
		for (int i = 0; i < ms; ++i) for (int k = 0; k < ms; ++k){
			MAT_R = matA[i][k];
			for (int j = 0; j < ms; ++j)
				MAT_RES.matA[i][j]+=(MAT_T.matA[k][j]*MAT_R);
		}
		return MAT_RES;
	}
	inline Matrix operator ^ (LL MAT_X) const {
		Matrix MAT_RES, MAT_BAS;
		for (int i = 0; i < ms; ++i) MAT_RES.matA[i][i] = 1;
		for (int i = 0; i < ms; ++i) for (int j = 0; j < ms; ++j)
			MAT_BAS.matA[i][j] = matA[i][j];
		while (MAT_X) {
			if (MAT_X&1)MAT_RES=MAT_RES*MAT_BAS; 
			MAT_BAS=MAT_BAS*MAT_BAS; MAT_X>>=1;
		}
		return MAT_RES;
	}
}ts;

char x[300];
LL S;

void main2() {
	ac.init();
	for (int i = 1; i <= n; ++i) {
		scanf("%s", x + 1);
		ac.insert(x, i);
	}
	ac.build();
	Matrix ans; ans.ms = 2;
	ans.matA[0][0] = ans.matA[1][0] = 1;
	LL res = 0;
	for (int i = 1; i <= 8; ++i) {
		LL tmp = 1;
		for (int j = 1; j <= i; ++j) {
			tmp *= 26;
		}
		res += tmp;
	}
	ans = (ts ^ m) * ans;
	S = ans.matA[0][0];
	Matrix A; A.ms = tot + 3;
	for (int i = 0; i <= tot; ++i) {
		if (bad[i]) continue;
		for (int j = 0; j < 26; ++j) {
			if (!bad[tr[i][j]]) {
				++A.matA[i][tr[i][j]];
			} 
		}
	}
	for (int i = 0; i <= tot + 1; ++i) {
		A.matA[i][tot + 1] = 1;
	}
	A = A ^ m;
	LL fix = 0;
	for (int i = 0; i <= tot + 1; ++i) {
		fix += A.matA[0][i];
	}
	printf("%I64u\n", S - fix);
}

int main() {
	ts.ms = 2;
	ts.matA[0][0] = 26; ts.matA[0][1] = 1;
	ts.matA[1][0] = 0; ts.matA[1][1] = 1;
	while (~scanf("%lld%lld", &n, &m)) {
		main2();
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值