学习笔记:AC自动机与动态规划

题目链接

P4052 [JSOI2007] 文本生成器

P3311 [SDOI2014] 数数

P2292 [HNOI2004] L语言(数据已加强)

[JSOI2007] 文本生成器

计数 DP

很显然的补集转换,设不可读文本数量为 \(sum\)\(Ans = 26^m - sum\)

\(f(i,j)\) 表示长度为 \(i\) 的串中,在 AC 自动机上第 \(j\) 个节点时的最大值。

先建出 Tire 图,显然,串中均无法匹配时,有 \(f(i,Tire_{pos\rightarrow j}) = \sum f(i-1,j)\)

\(sum=\sum f(m,i)\)

现在要解决的问题变成了如何判断串是否无法匹配。

有一个显然的结论,一个串后缀中有匹配,该串就合法(指能被匹配)。

要处理匹配问题,就要在建 Tire 图时,记一个数组 \(g_i\) 表示到第 \(i\) 个节点时,该串是否合法。

若一节点为一模式串结尾 \(g_i=1\)

有转移 \(g_i = g_i \ or \ g_{fail_i}\)

Code(C++):

#include<bits/stdc++.h>
#define forn(i,s,t) for(register int i=(s);i<=(t);++i)
using namespace std;
const int N = 1e4+3,M = 103,Mod = 1e4+7;
inline int q_pow(int p,int k) {
	int Ans = 1;
	while(k) ((k&1)?Ans=Ans*p%Mod:0),p=p*p%Mod,k>>=1;
	return Ans;
}
namespace AC {
	int Tire[N][26],Nxt[N],sl; bool idx[N];
	int f[M][N];
	inline void Ins(char *s) {
		static int p,c;
		p=0;
		for(register int i=0;s[i];++i) {
			c = s[i] - 'A';
			if(!Tire[p][c]) Tire[p][c] = ++sl;
			p = Tire[p][c];
		}
		idx[p] = 1;
	}
	inline void Bld() {
		static queue<int> q;
		forn(i,0,25) if(Tire[0][i]) q.push(Tire[0][i]);
		while(!q.empty()) {
			static int u;
			u = q.front(); q.pop();
			forn(i,0,25) 
				if(Tire[u][i])
					Nxt[Tire[u][i]]=Tire[Nxt[u]][i],q.push(Tire[u][i]),
					idx[Tire[u][i]]|=idx[Tire[Nxt[u]][i]];
				else 
					Tire[u][i] = Tire[Nxt[u]][i];
		}
	}
	inline void solve(int len) {
		f[0][0] = 1;
		forn(i,1,len) forn(j,0,sl) forn(k,0,25)
			if(!idx[Tire[j][k]]) 
				f[i][Tire[j][k]] = (f[i][Tire[j][k]] + f[i-1][j]) %Mod;
		int Ans = q_pow(26,len);
		forn(i,0,sl) Ans = (Ans - f[len][i] + Mod) %Mod;
		printf("%d\n",Ans);
	}
}
int n,m; char s[N];
int main() {
	scanf("%d%d",&n,&m);
	forn(i,1,n) scanf("%s",s),AC::Ins(s);
	AC::Bld();
	AC::solve(m);
	return 0;
}

[SDOI2014] 数数

数位 DP

与上题极其相似,对于 AC 自动机的处理几乎一模一样,给出数位 DP 的核心代码。

LL f[N][M][2][2];                              // f(dig,pos,lim,zr) 表示
LL dp(int dig,int pos,bool lim,bool zr) {      // 第dig位,与Tire图上位置为pos的点时,数字是否满,是否有先导0时的解
	if(!~dig) return !AC::g[pos];
	if(AC::g[pos]) return 0;
	if(~f[dig][pos][lim][zr])
		return f[dig][pos][lim][zr];
	int Lim = lim ? (n[dig] - '0') : 9,Ans = 0;
	forn(i,0,Lim)
		Ans = (Ans + dp(dig-1,(zr&&!i)?0:AC::Tire[pos][i],lim&&(Lim==i),zr&&!i))%Mod;
	return f[dig][pos][lim][zr] = Ans;
}

注意直接这样 DP 会多算一种为 0 的情况,所以最后的答案要减一。

[HNOI2004] L语言

状压 DP

观察 \(\mid s \mid\) 很小,可以状压求解。

\(g_i\) 表示在 Tire 图中,该节点的状态 \(s\)\(s\) 的第 \(i\) 位表示到该节点时,前面的第 \(i\) 个节点是否为一个模式串的结尾。

后面只需要一个状态 \(f\) 进行 DP 即可, \(f\) 的第 \(i\) 位表示在当前位置向前 \(i\) 个字符是否能作为一个合法前缀。

那么如果 \(f \cap g_i \neq \varnothing\) ,则将第 \(0\) 位赋值为 \(1\) ,字符串每向后一个字符,状态 \(f\) 向左移一位。

Code(C++):

#include<bits/stdc++.h>
#define forn(i,s,t) for(register int i=(s);i<=(t);++i)
using namespace std;
const int L = 2e6+3,N = 203;
namespace AC {
	int Tire[N][26],Nxt[N]; unsigned val[N]; bool S[N];
	inline void Ins(char *s) {
		static int sl,p,c;
		p = 0;
		for(register int i=0;s[i];++i) {
			c = s[i] - 'a';
			if(!Tire[p][c]) Tire[p][c] = ++sl;
			p = Tire[p][c];
		}
		S[p] = 1;
	}
	inline void Bld() {
		static queue<int> q,d;
		forn(i,0,25) if(Tire[0][i]) q.push(Tire[0][i]),d.push(1);
		while(!d.empty()) {
			static int u,dep;
			u = q.front(); q.pop();
			dep = d.front(); d.pop();
			val[u] = val[Nxt[u]];
			if(S[u]) val[u] |= 1u<<dep;
			forn(i,0,25)
				if(Tire[u][i])
					Nxt[Tire[u][i]] = Tire[Nxt[u]][i],
					q.push(Tire[u][i]),d.push(dep+1);
				else 
					Tire[u][i] = Tire[Nxt[u]][i];
		}
	}
}
int n,m; char s[N],T[L];
int main() {
	scanf("%d%d",&n,&m);
	forn(i,1,n) scanf("%s",s),AC::Ins(s);
	AC::Bld();
	forn(i,1,m) {
		scanf("%s",T);
		static int p,Ans; p = Ans = 0;
		static unsigned f; f = 1;
		for(register int i=0;T[i];++i) {
			static int c;
			c = T[i] - 'a';
			p = AC::Tire[p][c];
			f <<= 1;
			if(AC::val[p] & f) {
				f |= 1; Ans = i+1;
			}
		}
		printf("%d\n",Ans);
	} 
	return 0;
} 
posted @ 2020-12-27 16:43  AxDea  阅读(143)  评论(0编辑  收藏  举报