AC自動機與動態規劃
題目連結
[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)
#define enter putchar('\n')
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];
map<string,bool> mp1;
map<string,int> mp2;
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);
if(mp1[T]) {
printf("%d\n",mp2[T]);
continue ;
}
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);
mp2[T] = Ans;
}
return 0;
}