1. Keywords Search
题目链接:hdu - 2222
题意:n个模式串和一个文本串,问有多少个模式串在文本串中出现过
思路:AC自动机模板题,把n个模式串插入自动机中,然后文本串在自动机上暴力跳fail即可
#include <iostream> #include <algorithm> #include <cstring> #include <cstdio> #include <queue> using namespace std; struct node { int nex[26], fail, cnt; }; int T, n, sz; node aho[500010]; char s[1000010]; queue<int> q; void init() { while (!q.empty()) q.pop(); for (int i = 0; i < 500010; i++) { memset(aho[i].nex, 0, sizeof(aho[i].nex)); aho[i].fail = aho[i].cnt = 0; } sz = 1; } inline void insert(char *s) { int len = strlen(s), now = 0; for (int i = 0; i < len; i++) { int c = s[i] - 'a'; if (!aho[now].nex[c]) aho[now].nex[c] = sz++; now = aho[now].nex[c]; } aho[now].cnt++; } void build() { for (int i = 0; i < 26; i++) { if (0 == aho[0].nex[i]) continue; aho[aho[0].nex[i]].fail = 0; q.push(aho[0].nex[i]); } while (!q.empty()) { int u = q.front(); q.pop(); for (int i = 0; i < 26; i++) { if (aho[u].nex[i]) { aho[aho[u].nex[i]].fail = aho[aho[u].fail].nex[i]; q.push(aho[u].nex[i]); } else aho[u].nex[i] = aho[aho[u].fail].nex[i]; } } } int query(char *s) { int len = strlen(s), now = 0, res = 0; for (int i = 0; i < len; i++) { int c = s[i] - 'a'; now = aho[now].nex[c]; for (int t = now; t && -1 != aho[t].cnt; t = aho[t].fail) { res += aho[t].cnt; aho[t].cnt = -1; } } return res; } int main() { scanf("%d", &T); while (T--) { init(); scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%s", s); insert(s); } build(); scanf("%s", s); printf("%d ", query(s)); } return 0; }
2. 病毒侵袭
题目链接:hdu - 2896
题意:n个模式串和多个文本串,问每个文本串中出现了哪些模式串,一共有多少文本串出现了模式串
思路:AC自动机模板题,把n个模式串插入自动机中,然后每个文本串在自动机上暴力跳fail即可
#include <iostream> #include <algorithm> #include <cstring> #include <cstdio> #include <queue> #include <vector> using namespace std; const int N = 60010; const int M = 128; struct node { int nex[M], fail, cnt; }; node aho[N]; int n, m, sz, tot; char s[N]; queue<int> q; void init() { while (!q.empty()) q.pop(); for (int i = 0; i < N; i++) { memset(aho[i].nex, 0, sizeof(aho[i].nex)); aho[i].fail = aho[i].cnt = 0; } sz = 1; } void insert(char *s, int id) { int len = strlen(s), now = 0; for (int i = 0; i < len; i++) { int c = s[i]; if (!aho[now].nex[c]) aho[now].nex[c] = sz++; now = aho[now].nex[c]; } aho[now].cnt = id; } void build() { for (int i = 0; i < 128; i++) { if (0 == aho[0].nex[i]) continue; aho[aho[0].nex[i]].fail = 0; q.push(aho[0].nex[i]); } while (!q.empty()) { int u = q.front(); q.pop(); for (int i = 0; i < 128; i++) { if (aho[u].nex[i]) { aho[aho[u].nex[i]].fail = aho[aho[u].fail].nex[i]; q.push(aho[u].nex[i]); } else aho[u].nex[i] = aho[aho[u].fail].nex[i]; } } } void query(char *s, int id) { vector<int> res; int len = strlen(s), now = 0; for (int i = 0; i < len; i++) { int c = s[i]; now = aho[now].nex[c]; for (int t = now; t && -1 != aho[t].cnt; t = aho[t].fail) { if (0 == aho[t].cnt) continue; res.push_back(aho[t].cnt); } } if (res.size()) { sort(res.begin(), res.end()); tot += 1; printf("web %d:", id); for (int i = 0; i < res.size(); i++) printf(" %d", res[i]); printf(" "); } } int main() { sz = 1; scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%s", s); insert(s, i); } build(); scanf("%d", &m); for (int i = 1; i <= m; i++) { scanf("%s", s); query(s, i); } printf("total: %d ", tot); return 0; }
3. 病毒侵袭持续中
题目链接:hdu - 3065
题意:n个模式串和一个文本串,问每个模式串在文本串中各出现了多少次,每个模式串不相同
思路:AC自动机模板题,把n个模电插入自动机中,文本串在自动机上暴力跳fail统计每个模式串出现的次数即可
#include <iostream> #include <algorithm> #include <cstring> #include <cstdio> #include <queue> using namespace std; const int N = 2000010; const int M = 1010; struct node { int nex[26], fail, id; }; int n, sz, res[M]; char st[M][55], s[N]; node aho[100010]; queue<int> q; void init() { while (!q.empty()) q.pop(); for (int i = 0; i < 100010; i++) { memset(aho[i].nex, 0, sizeof(aho[i].nex)); aho[i].fail = aho[i].id = 0; } memset(res, 0, sizeof(res)); sz = 1; } void insert(char *s, int id) { int len = strlen(s), now = 0; for (int i = 0; i < len; i++) { int c = s[i] - 'A'; if (!aho[now].nex[c]) aho[now].nex[c] = sz++; now = aho[now].nex[c]; } aho[now].id = id; } void build() { for (int i = 0; i < 26; i++) { if (!aho[0].nex[i]) continue; aho[aho[0].nex[i]].fail = 0; q.push(aho[0].nex[i]); } while (!q.empty()) { int u = q.front(); q.pop(); for (int i = 0; i < 26; i++) { if (aho[u].nex[i]) { aho[aho[u].nex[i]].fail = aho[aho[u].fail].nex[i]; q.push(aho[u].nex[i]); } else aho[u].nex[i] = aho[aho[u].fail].nex[i]; } } } void query(char *s) { int len = strlen(s), now = 0; for (int i = 0; i < len; i++) { if (s[i] < 'A' || s[i] > 'Z') { now = 0; continue; } int c = s[i] - 'A'; now = aho[now].nex[c]; for (int t = now; t && -1 != aho[t].id; t = aho[t].fail) { if (0 == aho[t].id) continue; res[aho[t].id]++; } } } int main() { while (scanf("%d", &n) != EOF) { init(); for (int i = 1; i <= n; i++) { scanf("%s", st[i]); insert(st[i], i); } build(); scanf("%s", s); query(s); for (int i = 1; i <= n; i++) { if (0 == res[i]) continue; printf("%s: %d ", st[i], res[i]); } } return 0; }
4. 【模板】AC自动机(二次加强版)
题目链接:luogu - 5357
题意:n个模式串和一个文本串,问每个模式串在文本串中各出现了多少次,每个模式串可能相同
思路:由于每个模式串可能相同,所以如果像上题一样将文本串在自动机上暴力跳fail统计每个模式串出现的次数,会TLE,其实我们没有必要遇到一个节点就暴力跳一次fail,我们可以先记录下当前节点出现的次数,最后在拓扑排序的时候只用跳一遍fail,同时令aho[aho[u].fail].cnt += aho[u].cnt,这样就能统计出每个节点出现的次数,映射一下就能求出每个模式串出现的次数
#include <iostream> #include <algorithm> #include <cstring> #include <cstdio> #include <queue> using namespace std; struct node { int nex[26], fail, cnt; }; char st[200010], s[2000010]; int n, sz, mp[200010], ind[200010], res[200010]; node aho[200010]; queue<int> q; void insert(char *s, int id) { int len = strlen(s), now = 0; for (int i = 0; i < len; i++) { int c = s[i] - 'a'; if (!aho[now].nex[c]) aho[now].nex[c] = ++sz; now = aho[now].nex[c]; } mp[id] = now; } void build() { for (int i = 0; i < 26; i++) { if (0 == aho[0].nex[i]) continue; aho[aho[0].nex[i]].fail = 0; q.push(aho[0].nex[i]); ind[0]++; } while (!q.empty()) { int u = q.front(); q.pop(); for (int i = 0; i < 26; i++) { if (aho[u].nex[i]) { aho[aho[u].nex[i]].fail = aho[aho[u].fail].nex[i]; q.push(aho[u].nex[i]); ind[aho[aho[u].nex[i]].fail]++; } else aho[u].nex[i] = aho[aho[u].fail].nex[i]; } } } void query(char *s) { int len = strlen(s), now = 0; for (int i = 0; i < len; i++) { int c = s[i] - 'a'; now = aho[now].nex[c]; aho[now].cnt++; } } void topo() { for (int i = 1; i <= sz; i++) if (0 == ind[i]) q.push(i); while (!q.empty()) { int u = q.front(); q.pop(); res[u] = aho[u].cnt; ind[aho[u].fail]--; aho[aho[u].fail].cnt += aho[u].cnt; if (0 == ind[aho[u].fail]) q.push(aho[u].fail); } } int main() { sz = 1; scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%s", st); insert(st, i); } build(); scanf("%s", s); query(s); topo(); for (int i = 1; i <= n; i++) printf("%d ", res[mp[i]]); return 0; }
5. DNA Sequence
题目链接:poj - 2778
题意:有m种DNA序列是有疾病的,问有多少长度为n的DNA序列不包含任意一种有疾病的序列,DNA序列仅含A,T,C,G四种字符,1<=n<=2000000000
思路:如果n比较小,可以通过dp来解决,dp[i][j]表示长度为i,在自动机上j节点位置时的方案数,当n比较大时,则需要通过矩阵来加速
参考题解:https://blog.csdn.net/morgan_xww/article/details/7834801
参考资料:http://www.matrix67.com/blog/archives/276
#include <iostream> #include <algorithm> #include <cstring> #include <cstdio> #include <queue> using namespace std; typedef long long ll; const ll mod = 100000; struct node { int nex[4], fail, mk; }; struct mat { ll c[110][110]; }; int n, m, sz; node aho[110]; char s[15]; queue<int> q; mat mt, nuit; void init() { for (int i = 0; i < sz; i++) nuit.c[i][i] = 1; } int calc(char c) { if ('A' == c) return 0; if ('T' == c) return 1; if ('C' == c) return 2; return 3; } void insert(char *s) { int len = strlen(s), now = 0; for (int i = 0; i < len; i++) { int c = calc(s[i]); if (!aho[now].nex[c]) aho[now].nex[c] = sz++; now = aho[now].nex[c]; } aho[now].mk = 1; } void build() { for (int i = 0; i < 4; i++) { if (0 == aho[0].nex[i]) continue; aho[aho[0].nex[i]].fail = 0; q.push(aho[0].nex[i]); } while (!q.empty()) { int u = q.front(); q.pop(); if (aho[aho[u].fail].mk) aho[u].mk = 1; for (int i = 0; i < 4; i++) { if (aho[u].nex[i]) { aho[aho[u].nex[i]].fail = aho[aho[u].fail].nex[i]; q.push(aho[u].nex[i]); } else aho[u].nex[i] = aho[aho[u].fail].nex[i]; } } for (int i = 0; i < sz; i++) for (int k = 0; k < 4; k++) if (!aho[i].mk && !aho[aho[i].nex[k]].mk) mt.c[i][aho[i].nex[k]]++; } mat mul(mat a, mat b) { mat res; for (int i = 0; i < sz; i++) { for (int k = 0; k < sz; k++) { res.c[i][k] = 0; for (int t = 0; t < sz; t++) res.c[i][k] += a.c[i][t] * b.c[t][k]; res.c[i][k] %= mod; } } return res; } mat power(mat a, int n) { init(); mat res = nuit; while (n) { if (n & 1) res = mul(res, a); a = mul(a, a); n >>= 1; } return res; } int main() { sz = 1; scanf("%d%d", &m, &n); while (m--) { scanf("%s", s); insert(s); } build(); mat res = power(mt, n); ll sum = 0; for (int i = 0; i < sz; i++) sum += res.c[0][i]; printf("%lld ", sum % mod); return 0; }
6. 考研路茫茫——单词情结
题目链接:hdu - 2243
题意:给出m个单词,求长度不超过n且至少包含一个单词的串的数目
思路:和上题类似,我们求出长度不超过n且不包含任意一个单词的串的数目cnt,然后用总数减去cnt即可得到答案,但因为求的是长度不超过n的串的数目,不是长度恰好为n,所以矩阵需要增加一维,最后一列全部为1,这样就能求出长度不超过n且不包含任意一个单词的串的数目,总数为$26+26^2+cdots+26^n$,设f[n]=$1+26+26^2+cdots+26^n$,那么f[n]=26*f[n-1]+1,用矩阵快速幂加速,两者相减即可得到答案
#include <iostream> #include <algorithm> #include <cstring> #include <cstdio> #include <queue> using namespace std; typedef unsigned long long ull; struct mat { ull c[40][40]; }; struct node { int nex[26], fail, mk; }; node aho[40]; int n, m, sz, l; char s[10]; queue<int> q; mat mt, nuit; void init() { while (!q.empty()) q.pop(); for (int i = 0; i < 40; i++) { memset(aho[i].nex, 0, sizeof(aho[i].nex)); aho[i].fail = aho[i].mk = 0; } sz = 1; } void insert(char *s) { int len = strlen(s), now = 0; for (int i = 0; i < len; i++) { int c = s[i] - 'a'; if (!aho[now].nex[c]) aho[now].nex[c] = sz++; now = aho[now].nex[c]; } aho[now].mk = 1; } void build() { for (int i = 0; i < 26; i++) { if (0 == aho[0].nex[i]) continue; aho[aho[0].nex[i]].fail = 0; q.push(aho[0].nex[i]); } while (!q.empty()) { int u = q.front(); q.pop(); if (aho[aho[u].fail].mk) aho[u].mk = 1; for (int i = 0; i < 26; i++) { if (aho[u].nex[i]) { aho[aho[u].nex[i]].fail = aho[aho[u].fail].nex[i]; q.push(aho[u].nex[i]); } else aho[u].nex[i] = aho[aho[u].fail].nex[i]; } } memset(mt.c, 0, sizeof(mt.c)); for (int i = 0; i < sz; i++) for (int k = 0; k < 26; k++) if (!aho[i].mk && !aho[aho[i].nex[k]].mk) mt.c[i][aho[i].nex[k]]++; for (int i = 0; i <= sz; i++) mt.c[i][sz] = 1; } mat mul(mat a, mat b) { mat res; for (int i = 0; i <= l; i++) { for (int k = 0; k <= l; k++) { res.c[i][k] = 0; for (int t = 0; t <= l; t++) res.c[i][k] += a.c[i][t] * b.c[t][k]; } } return res; } mat power(mat a, int n) { for (int i = 0; i <= l; i++) nuit.c[i][i] = 1; mat res = nuit; while (n) { if (n & 1) res = mul(res, a); a = mul(a, a); n >>= 1; } return res; } int main() { while (scanf("%d%d", &n, &m) != EOF) { init(); for (int i = 1; i <= n; i++) { scanf("%s", s); insert(s); } build(); l = sz; mat ans = power(mt, m); ull res = 0; for (int i = 0; i <= sz; i++) res += ans.c[0][i]; l = 1; mt.c[0][0] = 26; mt.c[0][1] = 0; mt.c[1][0] = mt.c[1][1] = 1; ans = power(mt, m); printf("%llu ", ans.c[1][0] + ans.c[0][0] - res); } return 0; }
7. Censored!
题目链接:poj - 1625
题意:给出p个非法串,求长度为m且不包含非法串的串的数目
思路:将p个非法串插入自动机中,做上标记,dp[i][j]表示长度为i,在自动机上j节点位置时的方案数,最后的答案就是$sum_{i=0}^{sz}$dp[m][i],sz表示自动机的节点个数,注意dp时忽略非法节点,需要大整数
#include <iostream> #include <algorithm> #include <cstring> #include <cstdio> #include <queue> #include <map> using namespace std; struct node { int nex[55], fail, mk; }; struct BigInt { const static int mod = 10000; const static int dlen = 4; int a[100], len; BigInt() { memset(a, 0, sizeof(a)); len = 1; } BigInt(int v) { memset(a, 0, sizeof(a)); len = 0; do { a[len++] = v % mod; v /= mod; } while (v); } BigInt(const char *s) { memset(a, 0, sizeof(a)); int L = strlen(s), id = 0; len = L / dlen; if (L % dlen) len++; for (int i = L - 1; i >= 0; i -= dlen) { int t = 0, k = i - dlen + 1; if (k < 0) k = 0; for (int j = k; j <= i; j++) t = t * 10 + s[j] - '0'; a[id++] = t; } } BigInt operator + (const BigInt &b) const { BigInt res; res.len = max(len, b.len); for (int i = 0; i <= res.len; i++) res.a[i] = 0; for (int i = 0; i < res.len; i++) { res.a[i] += ((i < len) ? a[i] : 0) + ((i < b.len) ? b.a[i] : 0); res.a[i + 1] += res.a[i] / mod; res.a[i] %= mod; } if (res.a[res.len] > 0) res.len++; return res; } BigInt operator * (const BigInt &b) const { BigInt res; for (int i = 0; i < len; i++) { int up = 0; for (int j = 0; j < b.len; j++) { int t = a[i] * b.a[j] + res.a[i + j] + up; res.a[i + j] = t % mod; up = t / mod; } if (0 != up) res.a[i + b.len] = up; } res.len = len + b.len; while (res.a[res.len - 1] == 0 && res.len > 1) res.len--; return res; } void output() { printf("%d", a[len - 1]); for (int i = len - 2; i >= 0; i--) printf("%04d", a[i]); printf(" "); } }; int n, m, p, sz; node aho[110]; map<char, int> mp; char s[55]; queue<int> q; BigInt dp[55][110]; void insert(char *s) { int len = strlen(s), now = 0; for (int i = 0; i < len; i++) { int c = mp[s[i]]; if (!aho[now].nex[c]) aho[now].nex[c] = sz++; now = aho[now].nex[c]; } aho[now].mk = 1; } void build() { for (int i = 0; i < n; i++) { if (0 == aho[0].nex[i]) continue; aho[aho[0].nex[i]].fail = 0; q.push(aho[0].nex[i]); } while (!q.empty()) { int u = q.front(); q.pop(); if (aho[aho[u].fail].mk) aho[u].mk = 1; for (int i = 0; i < n; i++) { if (aho[u].nex[i]) { aho[aho[u].nex[i]].fail = aho[aho[u].fail].nex[i]; q.push(aho[u].nex[i]); } else aho[u].nex[i] = aho[aho[u].fail].nex[i]; } } } int main() { sz = 1; scanf("%d%d%d", &n, &m, &p); scanf("%s", s); for (int i = 0; i < n; i++) mp[s[i]] = i; for (int i = 1; i <= p; i++) { scanf("%s", s); insert(s); } build(); for (int i = 0; i <= m; i++) for (int k = 0; k < sz; k++) dp[i][k] = 0; dp[0][0] = 1; for (int i = 0; i < m; i++) { for (int j = 0; j < sz; j++) { if (aho[j].mk) continue; for (int k = 0; k < n; k++) { int v = aho[j].nex[k]; if (aho[v].mk) continue; dp[i + 1][v] = dp[i + 1][v] + dp[i][j]; } } } BigInt res = 0; for (int i = 0; i < sz; i++) res = res + dp[m][i]; res.output(); return 0; }
8. Wireless Password
题目链接:hdu - 2825
题意:给出m个模式串,求长度为n且至少包含m个模式串的串的数目,m<=10
思路:m的范围比较下,状压dp,dp[i][j][t]表示长度为i,在自动机上j节点位置,已经匹配的模式串的集合为t的方案数,有一个优化,如果dp[i][j][k]=0,那么可以直接continue
#include <iostream> #include <algorithm> #include <cstring> #include <cstdio> #include <queue> using namespace std; const int mod = 20090717; struct node { int nex[26], fail, v; }; node aho[110]; int n, m, k, sz; int dp[27][110][1 << 10], bc[1 << 10]; char s[15]; queue<int> q; void init() { while (!q.empty()) q.pop(); for (int i = 0; i < 110; i++) { memset(aho[i].nex, 0, sizeof(aho[i].nex)); aho[i].fail = aho[i].v = 0; } sz = 1; } void insert(char *s, int v) { int len = strlen(s), now = 0; for (int i = 0; i < len; i++) { int c = s[i] - 'a'; if (!aho[now].nex[c]) aho[now].nex[c] = sz++; now = aho[now].nex[c]; } aho[now].v |= v; } void build() { for (int i = 0; i < 26; i++) { if (0 == aho[0].nex[i]) continue; aho[aho[0].nex[i]].fail = 0; q.push(aho[0].nex[i]); } while (!q.empty()) { int u = q.front(); q.pop(); aho[u].v |= aho[aho[u].fail].v; for (int i = 0; i < 26; i++) { if (aho[u].nex[i]) { aho[aho[u].nex[i]].fail = aho[aho[u].fail].nex[i]; q.push(aho[u].nex[i]); } else aho[u].nex[i] = aho[aho[u].fail].nex[i]; } } } int main() { bc[0] = 0; for (int t = 1; t < (1 << 10); t++) bc[t] = bc[t >> 1] + (t & 1); while (scanf("%d%d%d", &n, &m, &k) && 0 != (n + m + k)) { init(); for (int i = 0; i < m; i++) { scanf("%s", s); insert(s, 1 << i); } build(); for (int i = 0; i <= n; i++) for (int j = 0; j < sz; j++) for (int t = 0; t < (1 << m); t++) dp[i][j][t] = 0; dp[0][0][0] = 1; for (int i = 0; i < n; i++) { for (int j = 0; j < sz; j++) { for (int t = 0; t < (1 << m); t++) { if (0 == dp[i][j][t]) continue; for (int p = 0; p < 26; p++) { int pt = aho[j].nex[p]; dp[i + 1][pt][t | aho[pt].v] += dp[i][j][t]; dp[i + 1][pt][t | aho[pt].v] %= mod; } } } } int res = 0; for (int t = 0; t < (1 << m); t++) { if (bc[t] < k) continue; for (int i = 0; i < sz; i++) res = (res + dp[n][i][t]) % mod; } printf("%d ", res); } return 0; }
9. Ring
题目链接:hdu - 2296
题意:给你m个单词,每个单词有一个权值(一个单词出现多次算多个权值),现在需要你构造一个长度不超过n的字符串,使得字符串的权值最大,如果有多个字符串,输出最短的字符串,如果仍然有多个字符串,输出字典序最小的
思路:dp[i][j]表示长度为i,在自动机上j节点位置时的最大权值,并用p[i][j]来记录路径,p[i][j]表示长度为i,在自动机上j节点位置时最大权值对应的串
#include <iostream> #include <algorithm> #include <cstring> #include <cstdio> #include <string> #include <queue> using namespace std; struct node { int nex[26], fail, val; }; int T, n, m, sz, dp[55][1110]; node aho[1110]; string p[55][1110]; char s[110][20]; queue<int> q; void init() { while (!q.empty()) q.pop(); for (int i = 0; i < 1110; i++) { memset(aho[i].nex, 0, sizeof(aho[i].nex)); aho[i].fail = aho[i].val = 0; } sz = 1; } void insert(char *s, int v) { int len = strlen(s), now = 0; for (int i = 0; i < len; i++) { int c = s[i] - 'a'; if (!aho[now].nex[c]) aho[now].nex[c] = sz++; now = aho[now].nex[c]; } aho[now].val += v; } void build() { for (int i = 0; i < 26; i++) { if (0 == aho[0].nex[i]) continue; aho[aho[0].nex[i]].fail = 0; q.push(aho[0].nex[i]); } while (!q.empty()) { int u = q.front(); q.pop(); aho[u].val += aho[aho[u].fail].val; for (int i = 0; i < 26; i++) { if (aho[u].nex[i]) { aho[aho[u].nex[i]].fail = aho[aho[u].fail].nex[i]; q.push(aho[u].nex[i]); } else aho[u].nex[i] = aho[aho[u].fail].nex[i]; } } } inline string comp(string a, string b) { if ("" == a) return b; if (a.size() != b.size()) return a.size() < b.size() ? a : b; return a < b ? a : b; } void solve() { for (int i = 0; i <= n; i++) { for (int k = 0; k < sz; k++) { dp[i][k] = -1; p[i][k] = ""; } } dp[0][0] = 0; for (int i = 0; i < n; i++) { for (int k = 0; k < sz; k++) { if (-1 == dp[i][k]) continue; for (int t = 0; t < 26; t++) { int v = aho[k].nex[t]; if (dp[i + 1][v] > dp[i][k] + aho[v].val) continue; if (dp[i + 1][v] < dp[i][k] + aho[v].val) { dp[i + 1][v] = dp[i][k] + aho[v].val; p[i + 1][v] = p[i][k] + char(t + 'a'); } else { p[i + 1][v] = comp(p[i + 1][v], p[i][k] + char(t + 'a')); } } } } int imax = 0; string res = ""; for (int i = 0; i <= n; i++) { for (int k = 0; k < sz; k++) { if (dp[i][k] > imax) { imax = dp[i][k]; res = p[i][k]; } else if (dp[i][k] == imax) { res = comp(res, p[i][k]); } } } if (0 == imax) printf(" "); else cout << res << endl; } int main() { scanf("%d", &T); while (T--) { init(); scanf("%d%d", &n, &m); for (int i = 1; i <= m; i++) scanf("%s", s[i]); for (int i = 1; i <= m; i++) { int val; scanf("%d", &val); insert(s[i], val); } build(); solve(); } return 0; }
10. DNA repair
题目链接:hdu - 2457
题意:给出n个模式串和一个文本串,问至少在文本串中修改多少个字符才能使文本串不包含任意一个模式串,字符集为A,T,C,G
思路:dp[i][j]表示长度为i,在自动机上j节点位置时最少需要修改的字符数量,然后向四个方向转移,如果当前字符等于转移的字符,则字符数量不变,否则字符数量需要加1
#include <iostream> #include <algorithm> #include <cstring> #include <cstdio> #include <queue> using namespace std; const int INF = 0x3f3f3f3f; struct node { int nex[4], fail, mk; }; int n, sz, dp[1010][1010], icas; node aho[1010]; char st[25], s[1010]; queue<int> q; void init() { while (!q.empty()) q.pop(); for (int i = 0; i < 1010; i++) { memset(aho[i].nex, 0, sizeof(aho[i].nex)); aho[i].fail = aho[i].mk = 0; } sz = 1; } int calc(char c) { if ('A' == c) return 0; if ('G' == c) return 1; if ('C' == c) return 2; return 3; } void insert(char *s) { int len = strlen(s), now = 0; for (int i = 0; i < len; i++) { int c = calc(s[i]); if (!aho[now].nex[c]) aho[now].nex[c] = sz++; now = aho[now].nex[c]; } aho[now].mk = 1; } void build() { for (int i = 0; i < 4; i++) { if (0 == aho[0].nex[i]) continue; aho[aho[0].nex[i]].fail = 0; q.push(aho[0].nex[i]); } while (!q.empty()) { int u = q.front(); q.pop(); if (aho[aho[u].fail].mk) aho[u].mk = 1; for (int i = 0; i < 4; i++) { if (aho[u].nex[i]) { aho[aho[u].nex[i]].fail = aho[aho[u].fail].nex[i]; q.push(aho[u].nex[i]); } else aho[u].nex[i] = aho[aho[u].fail].nex[i]; } } } int query(char *s) { int len = strlen(s + 1); for (int i = 1; i <= len; i++) for (int k = 0; k < sz; k++) dp[i][k] = INF; for (int i = 0; i < len; i++) { for (int k = 0; k < sz; k++) { if (INF == dp[i][k]) continue; if (aho[k].mk) continue; for (int t = 0; t < 4; t++) { int v = aho[k].nex[t], c = calc(s[i + 1]); if (aho[v].mk) continue; if (t == c) dp[i + 1][v] = min(dp[i + 1][v], dp[i][k]); else dp[i + 1][v] = min(dp[i + 1][v], dp[i][k] + 1); } } } int imin = INF; for (int i = 0; i < sz; i++) imin = min(imin, dp[len][i]); return INF == imin ? -1 : imin; } int main() { while (scanf("%d", &n) && 0 != n) { init(); for (int i = 1; i <= n; i++) { scanf("%s", st); insert(st); } build(); scanf("%s", s + 1); printf("Case %d: %d ", ++icas, query(s)); } return 0; }
11 .Lost's revenge
题目链接:hdu - 3341
题意:给出n个模式串和一个文本串,问将文本串的字符重新排序后最多能包含多少个模式串(可以重叠),字符集为A,T,C,G
思路:dp[i][a][b][c][d]表示在自动机上i节点位置,已经使用过a个A,b个T,c个C,d个G时的最大值,但是这样会MLE,所以令h[a][b][c][d]=t,然后用dp[i][t]转移即可
#include <iostream> #include <algorithm> #include <cstring> #include <cstdio> #include <queue> using namespace std; struct node { int nex[4], fail, cnt; }; node aho[510]; int n, sz, f[510][15010], h[45][45][45][45]; int ct[4], icas; queue<int> q; char st[15], s[45]; void init() { while (!q.empty()) q.pop(); for (int i = 0; i < 510; i++) { memset(aho[i].nex, 0, sizeof(aho[i].nex)); aho[i].fail = aho[i].cnt = 0; } sz = 1; } int calc(char c) { if ('A' == c) return 0; if ('C' == c) return 1; if ('G' == c) return 2; return 3; } void insert(char *s) { int len = strlen(s), now = 0; for (int i = 0; i < len; i++) { int c = calc(s[i]); if (!aho[now].nex[c]) aho[now].nex[c] = sz++; now = aho[now].nex[c]; } aho[now].cnt++; } void build() { for (int i = 0; i < 4; i++) { if (0 == aho[0].nex[i]) continue; aho[aho[0].nex[i]].fail = 0; q.push(aho[0].nex[i]); } while (!q.empty()) { int u = q.front(); q.pop(); aho[u].cnt += aho[aho[u].fail].cnt; for (int i = 0; i < 4; i++) { if (aho[u].nex[i]) { aho[aho[u].nex[i]].fail = aho[aho[u].fail].nex[i]; q.push(aho[u].nex[i]); } else aho[u].nex[i] = aho[aho[u].fail].nex[i]; } } } int query(char *s) { memset(ct, 0, sizeof(ct)); int len = strlen(s), cnt = 0; for (int i = 0; i < len; i++) ct[calc(s[i])]++; for (int a = 0; a <= ct[0]; a++) for (int b = 0; b <= ct[1]; b++) for (int c = 0; c <= ct[2]; c++) for (int d = 0; d <= ct[3]; d++) h[a][b][c][d] = cnt++; for (int i = 0; i < sz; i++) for (int k = 0; k < cnt; k++) f[i][k] = -1; f[0][0] = 0; for (int a = 0; a <= ct[0]; a++) { for (int b = 0; b <= ct[1]; b++) { for (int c = 0; c <= ct[2]; c++) { for (int d = 0; d <= ct[3]; d++) { for (int i = 0; i < sz; i++) { int hu = h[a][b][c][d]; if (-1 == f[i][hu]) continue; if (a < ct[0]) { int v = aho[i].nex[0]; int hv = h[a + 1][b][c][d]; f[v][hv] = max(f[v][hv], f[i][hu] + aho[v].cnt); } if (b < ct[1]) { int v = aho[i].nex[1]; int hv = h[a][b + 1][c][d]; f[v][hv] = max(f[v][hv], f[i][hu] + aho[v].cnt); } if (c < ct[2]) { int v = aho[i].nex[2]; int hv = h[a][b][c + 1][d]; f[v][hv] = max(f[v][hv], f[i][hu] + aho[v].cnt); } if (d < ct[3]) { int v = aho[i].nex[3]; int hv = h[a][b][c][d + 1]; f[v][hv] = max(f[v][hv], f[i][hu] + aho[v].cnt); } } } } } } int hm = h[ct[0]][ct[1]][ct[2]][ct[3]], imax = 0; for (int i = 0; i < sz; i++) imax = max(imax, f[i][hm]); return imax; } int main() { while (scanf("%d", &n) && 0 != n) { init(); for (int i = 1; i <= n; i++) { scanf("%s", st); insert(st); } build(); scanf("%s", s); printf("Case %d: %d ", ++icas, query(s)); } return 0; }
12. Resource Archiver
题目链接:hdu - 3247
题意:n个合法串,m个非法串,问包含所有合法串(可以重叠)但不包含非法串、长度最短的串的长度为多少,n<=10
思路:将所有合法串和非法串插入自动机中,做上相应标记(合法串用状压,非法串用-1),然后从每个合法串开始进行bfs,得到每个合法串到其他合法串的距离(不经过非法串),dp[i][j]表示合法串的集合为i,在自动机上j节点时的最短长度,dp时只用在合法节点之间转移即可
#include <iostream> #include <algorithm> #include <cstring> #include <cstdio> #include <queue> using namespace std; const int INF = 0x3f3f3f3f; struct node { int nex[2], fail, v; }; int n, m, sz, pos[15], d[15][15]; int dis[60010], dp[1100][15]; node aho[60010]; char s[50010]; queue<int> q; void init() { while (!q.empty()) q.pop(); for (int i = 0; i < 60010; i++) { memset(aho[i].nex, 0, sizeof(aho[i].nex)); aho[i].fail = aho[i].v = 0; } sz = 1; } void insert(char *s, int v) { int len = strlen(s), now = 0; for (int i = 0; i < len; i++) { int c = s[i] - '0'; if (!aho[now].nex[c]) aho[now].nex[c] = sz++; now = aho[now].nex[c]; } aho[now].v = v; } void build() { for (int i = 0; i < 2; i++) { if (0 == aho[0].nex[i]) continue; aho[aho[0].nex[i]].fail = 0; q.push(aho[0].nex[i]); } while (!q.empty()) { int u = q.front(); q.pop(); if (-1 == aho[aho[u].fail].v) aho[u].v = -1; else aho[u].v |= aho[aho[u].fail].v; for (int i = 0; i < 2; i++) { if (aho[u].nex[i]) { aho[aho[u].nex[i]].fail = aho[aho[u].fail].nex[i]; q.push(aho[u].nex[i]); } else aho[u].nex[i] = aho[aho[u].fail].nex[i]; } } } void bfs(int s, int sum) { for (int i = 0; i < sz; i++) dis[i] = -1; queue<int> sq; sq.push(pos[s]); dis[pos[s]] = 0; while (!sq.empty()) { int u = sq.front(); sq.pop(); for (int i = 0; i < 2; i++) { int v = aho[u].nex[i]; if (-1 == dis[v] && -1 != aho[v].v) { dis[v] = dis[u] + 1; sq.push(v); } } } for (int i = 0; i <= sum; i++) d[s][i] = dis[pos[i]]; } int main() { while (scanf("%d%d", &n, &m) && 0 != (n + m)) { init(); for (int i = 0; i < n; i++) { scanf("%s", s); insert(s, 1 << i); } for (int i = 0; i < m; i++) { scanf("%s", s); insert(s, -1); } build(); int sum = 0; for (int i = 0; i < sz; i++) if (aho[i].v > 0) pos[++sum] = i; bfs(0, sum); for (int i = 1; i <= sum; i++) bfs(i, sum); memset(dp, INF, sizeof(dp)); dp[0][0] = 0; for (int i = 0; i < (1 << n); i++) { for (int k = 0; k <= sum; k++) { if (INF == dp[i][k]) continue; for (int t = 0; t <= sum; t++) { if (k == t || -1 == d[k][t]) continue; int nt = pos[t], v = i | aho[nt].v; dp[v][t] = min(dp[v][t], dp[i][k] + d[k][t]); } } } int res = INF; for (int i = 1; i <= sum; i++) res = min(res, dp[(1 << n) - 1][i]); printf("%d ", res); } return 0; }
13. Walk Through Squares
题目链接:hdu - 4758
题意:字符集为R、D,给你两个模式串,问你用n个R,m个D能构造多少个文本串包含这两个模式串
思路:状压dp,dp[i][j][k][t]表示在自动机上i节点位置,已经用过了j个R,k个D,并且模式串的集合为t时的方案数
#include <iostream> #include <algorithm> #include <cstring> #include <cstdio> #include <queue> using namespace std; const int mod = 1000000007; struct node { int nex[2], fail, v; }; node aho[210]; int T, m, n, sz, f[210][110][110][4]; char s[110]; queue<int> q; void init() { while (!q.empty()) q.pop(); for (int i = 0; i < 210; i++) { memset(aho[i].nex, 0, sizeof(aho[i].nex)); aho[i].fail = aho[i].v = 0; } sz = 1; } inline int calc(char c) { if ('R' == c) return 0; return 1; } void insert(char *s, int v) { int len = strlen(s), now = 0; for (int i = 0; i < len; i++) { int c = calc(s[i]); if (!aho[now].nex[c]) aho[now].nex[c] = sz++; now = aho[now].nex[c]; } aho[now].v |= v; } void build() { for (int i = 0; i < 2; i++) { if (0 == aho[0].nex[i]) continue; aho[aho[0].nex[i]].fail = 0; q.push(aho[0].nex[i]); } while (!q.empty()) { int u = q.front(); q.pop(); aho[u].v |= aho[aho[u].fail].v; for (int i = 0; i < 2; i++) { if (aho[u].nex[i]) { aho[aho[u].nex[i]].fail = aho[aho[u].fail].nex[i]; q.push(aho[u].nex[i]); } else aho[u].nex[i] = aho[aho[u].fail].nex[i]; } } } int query() { for (int i = 0; i < sz; i++) for (int a = 0; a <= m; a++) for (int b = 0; b <= n; b++) for (int t = 0; t < 4; t++) f[i][a][b][t] = 0; f[0][0][0][0] = 1; for (int a = 0; a <= m; a++) { for (int b = 0; b <= n; b++) { for (int i = 0; i < sz; i++) { for (int t = 0; t < 4; t++) { if (0 == f[i][a][b][t]) continue; int &val = f[i][a][b][t]; if (a < m) { int v = aho[i].nex[0]; f[v][a + 1][b][t | aho[v].v] += val; f[v][a + 1][b][t | aho[v].v] %= mod; } if (b < n) { int v = aho[i].nex[1]; f[v][a][b + 1][t | aho[v].v] += val; f[v][a][b + 1][t | aho[v].v] %= mod; } } } } } int res = 0; for (int i = 0; i < sz; i++) res = (res + f[i][m][n][3]) % mod; return res; } int main() { scanf("%d", &T); while (T--) { init(); scanf("%d%d", &m, &n); for (int i = 0; i < 2; i++) { scanf("%s", s); insert(s, 1 << i); } build(); printf("%d ", query()); } return 0; }
14. 小明系列故事——女友的考验
题目链接:hdu - 4511
题意:给定n个点的坐标和m个非法子路径,每次只能走向编号比自己大的点且不能经过非法子路径,求1到n的最短路径,n<=50
思路:将m个非法子路径插入到自动机中,dp[i][j]表示在编号为i的点,自动机上j节点时的最短路径,注意dp的时候只能向编号比自己大的点转移
#include <iostream> #include <algorithm> #include <cstring> #include <cstdio> #include <queue> #include <cmath> using namespace std; const double INF = 1e18; struct point { double x, y; }; struct node { int nex[55], fail, mk; }; node aho[510]; int n, m, sz, s[10]; double f[55][510]; point p[55]; queue<int> q; void init() { while (!q.empty()) q.pop(); for (int i = 0; i < 510; i++) { memset(aho[i].nex, 0, sizeof(aho[i].nex)); aho[i].fail = aho[i].mk = 0; } sz = 1; } void insert(int len) { int now = 0; for (int i = 1; i <= len; i++) { int c = s[i]; if (!aho[now].nex[c]) aho[now].nex[c] = sz++; now = aho[now].nex[c]; } aho[now].mk = 1; } void build() { for (int i = 1; i <= n; i++) { if (0 == aho[0].nex[i]) continue; aho[aho[0].nex[i]].fail = 0; q.push(aho[0].nex[i]); } while (!q.empty()) { int u = q.front(); q.pop(); if (aho[aho[u].fail].mk) aho[u].mk = 1; for (int i = 1; i <= n; i++) { if (aho[u].nex[i]) { aho[aho[u].nex[i]].fail = aho[aho[u].fail].nex[i]; q.push(aho[u].nex[i]); } else aho[u].nex[i] = aho[aho[u].fail].nex[i]; } } } double dis(point a, point b) { double dx = a.x - b.x; double dy = a.y - b.y; double res = dx * dx + dy * dy; return sqrt(res); } double solve() { for (int i = 1; i <= n; i++) for (int k = 0; k < sz; k++) f[i][k] = INF; f[1][aho[0].nex[1]] = 0; for (int i = 1; i < n; i++) { for (int k = 0; k < sz; k++) { if (f[i][k] == INF) continue; for (int t = i + 1; t <= n; t++) { int v = aho[k].nex[t]; if (aho[v].mk) continue; f[t][v] = min(f[t][v], f[i][k] + dis(p[i], p[t])); } } } double res = INF; for (int i = 0; i < sz; i++) { if (INF == f[n][i]) continue; res = min(res, f[n][i]); } return res; } int main() { while (scanf("%d%d", &n, &m) && 0 != (n + m)) { init(); for (int i = 1; i <= n; i++) scanf("%lf%lf", &p[i].x, &p[i].y); for (int i = 1; i <= m; i++) { int k; scanf("%d", &k); for (int t = 1; t <= k; t++) scanf("%d", &s[t]); insert(k); } build(); double res = solve(); if (INF == res) printf("Can not be reached! "); else printf("%.2lf ", res); } return 0; }