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;
    } 
    
  • 相关阅读:
    101. Symmetric Tree(js)
    100. Same Tree(js)
    99. Recover Binary Search Tree(js)
    98. Validate Binary Search Tree(js)
    97. Interleaving String(js)
    96. Unique Binary Search Trees(js)
    95. Unique Binary Search Trees II(js)
    94. Binary Tree Inorder Traversal(js)
    93. Restore IP Addresses(js)
    92. Reverse Linked List II(js)
  • 原文地址:https://www.cnblogs.com/Ax-Dea/p/14195215.html
Copyright © 2011-2022 走看看