zoukankan      html  css  js  c++  java
  • 学习笔记: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 ightarrow 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
    ",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 eq 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
    ",Ans);
    	} 
    	return 0;
    } 
    
  • 相关阅读:
    《冒号课堂》学习笔记 OOP-继承
    《冒号课堂》学习笔记 编程范式汇总
    EF中主表和附表一起提交的话,如果主附表的主键外键已经设定。
    超时时间已到。在操作完成之前超时时间已过或服务器未响应。 解决方法
    wpf下拉框不能多选的原因
    查询中无法构造实体或复杂类型
    wpf新增记录时用多线程的问题
    面向对象
    HTML5入门以及新标签
    前端学习本地存储
  • 原文地址:https://www.cnblogs.com/Ax-Dea/p/14195215.html
Copyright © 2011-2022 走看看