1. 程式人生 > >BZOJ1559 [JSOI2009]密碼 【AC自動機 + 狀壓dp】

BZOJ1559 [JSOI2009]密碼 【AC自動機 + 狀壓dp】

包含關系 || fail queue string ron 密碼 sam lse

題目鏈接

BZOJ1559

題解

考慮到這是一個包含子串的問題,而且子串非常少,我們考慮\(AC\)自動機上的狀壓\(dp\)
\(f[i][j][s]\)表示長度為\(i\)的串,匹配到了\(AC\)自動機\(j\)號節點,且已匹配集合為\(s\)的方案數
直接在\(AC\)自動機上轉移即可
但是為了防止使用\(last\)指針之類的,計算匹配的串,我們先將原串的集合去重和去包含關系

方案怎麽辦?
考慮到\(ans \le 42\),一定是剛好若幹個原串以最長前後綴相同的方式相接
因為如果不是以最長前後綴的方式相接,那麽一定存在更小的方案,這個時候就會多出若幹空位置可以隨便填
一個位置可以填\(26\)

種字母,而且可以放在前綴或者後綴,所以有一個空位置就有\(52\)種方案,大於\(42\)
所以可以放心\(O(n!)\)枚舉排列相接

\(1A\)了很開心

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
#include<cmath>
#include<queue>
#include<map>
#define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
#define REP(i,n) for (int i = 1; i <= (n); i++) #define mp(a,b) make_pair<int,int>(a,b) #define cls(s) memset(s,0,sizeof(s)) #define cp pair<int,int> #define LL long long int using namespace std; const int maxn = 105,maxm = 100005,INF = 1000000000; inline int read(){ int out = 0,flag = 1; char c = getchar(); while
(c < 48 || c > 57){if (c == ‘-‘) flag = -1; c = getchar();} while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();} return out * flag; } int ch[maxn][26],fail[maxn],tag[maxn],cnt,len[12]; char s[12][12]; int isin[maxn]; int same[12][12]; LL f[26][105][1 << 10]; void ins(int p){ int u = 0,id; for (int i = 1; i <= len[p]; i++){ id = s[p][i] - ‘a‘; u = ch[u][id] ? ch[u][id] : (ch[u][id] = ++cnt); } tag[u] |= (1 << p - 1); } void getf(){ queue<int> q; for (int i = 0; i < 26; i++) if (ch[0][i]) q.push(ch[0][i]); int u,v; while (!q.empty()){ u = q.front(); q.pop(); for (int i = 0; i < 26; i++){ v = ch[u][i]; if (!v){ ch[u][i] = ch[fail[u]][i]; continue; } fail[v] = ch[fail[u]][i]; q.push(v); } } } int L,n; int check(int u,int v){ if (len[u] > len[v]) return 0; if (len[u] == len[v]){ for (int i = 1; i <= len[u]; i++) if (s[u][i] != s[v][i]) return 0; return 1; } for (int i = 1; i <= len[v] - len[u] + 1; i++){ int flag = true; for (int j = 1; j <= len[u]; j++){ if (s[u][j] != s[v][i + j - 1]){ flag = false; break; } } if (flag) return 2; } return 0; } int cal(int u,int v){ for (int i = max(1,len[u] - len[v] + 1); i <= len[u]; i++){ int flag = true; for (int j = 1; i + j - 1 <= len[u]; j++) if (s[u][i + j - 1] != s[v][j]){ flag = false; break; } if (flag) return len[u] - i + 1; } return 0; } int vis[maxn],a[maxn],ansi,Ltot; struct node{ char s[30]; int len; }ans[50]; inline bool operator <(const node& a,const node& b){ return strcmp(a.s + 1,b.s + 1) < 0; } void Check(){ int sum = Ltot; for (int i = 1; i < n; i++) sum -= same[a[i]][a[i + 1]]; if (sum == L){ ansi++; for (int i = 1; i <= n; i++){ int u = a[i]; for (int j = same[a[i - 1]][u] + 1; j <= len[u]; j++){ ans[ansi].s[++ans[ansi].len] = s[u][j]; } } } } void dfs(int u){ if (u > n){ Check(); return; } for (int i = 1; i <= n; i++) if (!vis[i]){ vis[i] = true; a[u] = i; dfs(u + 1); vis[i] = false; } } void work(){ for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) if (i != j) same[i][j] = cal(i,j); for (int i = 1; i <= n; i++) Ltot += len[i]; dfs(1); sort(ans + 1,ans + 1 + ansi); for (int i = 1; i <= ansi; i++) printf("%s\n",ans[i].s + 1); } int main(){ L = read(); n = read(); for (int i = 1; i <= n; i++){ scanf("%s",s[i] + 1); len[i] = strlen(s[i] + 1); } for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) if (i != j ){ int t = check(i,j); if (t == 2 || (t == 1 && i > j)) isin[i] = true; } int tot = 0; for (int i = 1; i <= n; i++) if (!isin[i]){ strcpy(s[++tot] + 1,s[i] + 1); len[tot] = len[i]; ins(tot); } n = tot; getf(); int maxv = (1 << n) - 1,u; f[0][0][0] = 1; for (int i = 0; i < L; i++){ for (int j = 0; j <= cnt; j++){ for (int s = 0; s <= maxv; s++){ if (!f[i][j][s]) continue; for (int k = 0; k < 26; k++){ u = ch[j][k]; f[i + 1][u][s | tag[u]] += f[i][j][s]; } } } } LL ans = 0; for (int i = 0; i <= cnt; i++) ans += f[L][i][maxv]; printf("%lld\n",ans); if (ans <= 42) work(); return 0; }

BZOJ1559 [JSOI2009]密碼 【AC自動機 + 狀壓dp】