AC自动机
AC自动机, (Aho-Corasick automaton). AC自动机是什么? 是在Trie树上建边,形成的一个图的算法. AC自动机用来干什么? 构建状态转移的图.(维护"路径"!!) 1. 维护路径是否可以走. 2. 维护路径的值. 如何构建AC自动机. 1. 先建立一棵Trie树. 2. 在Trie树上建我们需要的边.维护节点信息. Solved 1 / 1 A HDU 2222 Keywords Search 题意: 给定n个模式串,然后给一个文本串,问出现了多少个文本串. (串只有小写字母) N <= 10000 |S|<50 |S|<1000000 分析: 裸的AC自动机题.
#include <queue> #include <cstdio> #include <cstring> using namespace std; const int maxn = 1e6+500; char str[maxn]; class AC { int next[maxn][26]; int fail[maxn]; int ed[maxn]; int last[maxn]; int qsz, root; inline int getid(char ch) { return ch - 'a'; } inline int newnode() { int i; for (i=0; i<26; ++i) next[qsz][i] = 0; fail[qsz] = last[qsz] = ed[qsz] = 0; return qsz++; } public: void init() { qsz = 0; root = newnode(); } void insert(char s[]) { int i, id, rt = 0, len = strlen(s); for (i=0; i<len; ++i) { id = getid(s[i]); if (!next[rt][id]) next[rt][id] = newnode(); rt = next[rt][id]; } ed[rt]++; } void build() { queue<int> q; int rt, i; fail[root] = last[root] = rt = root; for (i=0; i<26; ++i) if (next[rt][i]) q.push(next[rt][i]); while (!q.empty()) { rt = q.front(); q.pop(); for (i=0; i<26; ++i) { if (!next[rt][i]) next[rt][i] = next[fail[rt]][i]; else { fail[next[rt][i]] = next[fail[rt]][i]; last[next[rt][i]] = ed[fail[next[rt][i]]] ? fail[next[rt][i]] : last[next[rt][i]]; q.push(next[rt][i]); } } } } int query(char s[]) { int rt = 0, tmp, i, len = strlen(s); int res = 0; for (i=0; i<len; ++i) { tmp = next[rt][getid(s[i])]; while (tmp) { res += ed[tmp]; ed[tmp] = 0; tmp = last[tmp]; } rt = next[rt][getid(s[i])]; } return res; } }ac; int main() { int t, n, i; scanf("%d", &t); while (t--) { ac.init(); scanf("%d", &n); for (i=0; i<n; ++i) { scanf("%s", str); ac.insert(str); } ac.build(); scanf("%s", str); printf("%d\n", ac.query(str)); } return 0; }
Solved 1 / 2 B HDU 2896 病毒侵袭 题意: 给定n个模式串标号为1..n m个文本串, 问每个文本串中出现模式串的标号,最后统计有出现模式串的文本串个数. 每个文本串不会出现超过3个模式串. (注意ASCII的范围是可见字符) 1<=N<=500 20<|S|<200 1<=M<=1000 2000<|S|<10000 分析: 还是裸的AC自动机.对于每个文本串,我们可以用一个set来记录出现的模式串.
#include <set> #include <queue> #include <cstdio> #include <cstring> using namespace std; const int maxn = 1e5+500; char str[maxn]; set<int> se[1024]; class AC { int next[maxn][128]; int fail[maxn]; int ed[maxn]; int last[maxn]; int qsz, root; inline int getid(char ch) { return ch - 32; } inline int newnode() { int i; for (i=0; i<128; ++i) next[qsz][i] = 0; fail[qsz] = last[qsz] = ed[qsz] = 0; return qsz++; } public: void init() { qsz = 0; root = newnode(); } void insert(char s[], int tid) { int i, id, rt = 0, len = strlen(s); for (i=0; i<len; ++i) { id = getid(s[i]); if (!next[rt][id]) next[rt][id] = newnode(); rt = next[rt][id]; } ed[rt] = tid; } void build() { queue<int> q; int rt, i; fail[root] = last[root] = rt = root; for (i=0; i<128; ++i) if (next[rt][i]) q.push(next[rt][i]); while (!q.empty()) { rt = q.front(); q.pop(); for (i=0; i<128; ++i) { if (!next[rt][i]) next[rt][i] = next[fail[rt]][i]; else { fail[next[rt][i]] = next[fail[rt]][i]; last[next[rt][i]] = ed[fail[next[rt][i]]] ? fail[next[rt][i]] : last[next[rt][i]]; q.push(next[rt][i]); } } } } bool query(char s[], set<int> &se) { int rt = 0, tmp, i, len = strlen(s); bool ishave = false; for (i=0; i<len; ++i) { tmp = next[rt][getid(s[i])]; while (tmp) { if (ed[tmp]) se.insert(ed[tmp]), ishave = true; tmp = last[tmp]; } rt = next[rt][getid(s[i])]; } return ishave; } }ac; int main() { int t, n, i; ac.init(); scanf("%d", &n); for (i=1; i<=n; ++i) { scanf("%s", str); ac.insert(str, i); } ac.build(); scanf("%d", &n); int tot = 0; for (i=1; i<=n; ++i) { scanf("%s", str); tot += ac.query(str, se[i]); } for (i=1; i<=n; ++i) { if (se[i].empty()) continue; printf("web %d: %d", i, *se[i].begin()); se[i].erase(se[i].begin()); for (auto it : se[i]) printf(" %d", it); printf("\n"); } printf("total: %d\n", tot); return 0; }
Solved 1 / 2 C HDU 3065 病毒侵袭持续中 题意: 给n个模式串, 1个文本串, 问每个模式串在文本串中出现的次数 最后输出出现过的模式串 和 出现次数. (注意ASCII的范围是可见字符-- 当然,这个题也可以处理下,然后就只有大写字母, 因为模式串只有大写字母: 文本串我们只需要把它分割, 以每一段仅由大写字母组成的作为文本串进行匹配,不过没有卡空间,就随便啦.{我没被卡}) 1<=N<=1000 |S|<50 |S|<2e6 分析: 又是板子题.和上一题类似的.
#include <queue> #include <cstdio> #include <cstring> using namespace std; const int maxn = 2e6+500; const int cmaxn = 1e5; char str[maxn]; char ss[1024][52]; int ans[1024]; class AC { public: int next[cmaxn][128]; int fail[cmaxn]; int ed[cmaxn]; int last[cmaxn]; int qsz, root; inline int getid(char ch) { return ch - 32; } inline int newnode() { int i; for (i=0; i<128; ++i) next[qsz][i] = 0; fail[qsz] = last[qsz] = ed[qsz] = 0; return qsz++; } void init() { qsz = 0; root = newnode(); } void insert(char s[], int tid) { int i, id, rt = 0, len = strlen(s); for (i=0; i<len; ++i) { id = getid(s[i]); if (!next[rt][id]) next[rt][id] = newnode(); rt = next[rt][id]; } ed[rt] = tid; } void build() { queue<int> q; int rt, i; fail[root] = last[root] = rt = root; for (i=0; i<128; ++i) if (next[rt][i]) q.push(next[rt][i]); while (!q.empty()) { rt = q.front(); q.pop(); for (i=0; i<128; ++i) { if (!next[rt][i]) next[rt][i] = next[fail[rt]][i]; else { fail[next[rt][i]] = next[fail[rt]][i]; last[next[rt][i]] = ed[fail[next[rt][i]]] ? fail[next[rt][i]] : last[next[rt][i]]; q.push(next[rt][i]); } } } } int query(char s[]) { int rt = 0, tmp, i, len = strlen(s); for (i=0; i<len; ++i) { rt = tmp = next[rt][getid(s[i])]; while (tmp) { if (ed[tmp]) ans[ed[tmp]]++; tmp = last[tmp]; } } } }ac; int main() { int t, n, i; while (~scanf("%d", &n)) { ac.init(); memset(ans, 0, sizeof(ans)); for (i=1; i<=n; ++i) { scanf("%s", ss[i]); ac.insert(ss[i], i); } ac.build(); scanf("%s", str); ac.query(str); for (i=1; i<=n; ++i) if (ans[i]) printf("%s: %d\n", ss[i], ans[i]); } return 0; }
Solved 1 / 7 D ZOJ 3430 Detect the Virus 题意: 给定n个Base编码后的模式串,m个Base编码的文本串, 输出在文本串中出现的模式串的个数. 0 <= N <= 512 1 <= M <= 128 分析: 将编码解码后就是一道裸的AC自动机的题了.
#include <cstdio> #include <queue> #include <map> #include <set> using namespace std; map<int, int> ma; int pwr[] = {1, 2, 4, 8, 16, 32, 64, 128}; void init() { int i, id = 0; for (i='A'; i<='Z'; ++i) ma[i] = id++; for (i='a'; i<='z'; ++i) ma[i] = id++; for (i='0'; i<='9'; ++i) ma[i] = id++; ma['+'] = id++; ma['/'] = id++; } char str[100086]; int bits[100086]; int ssss[100086]; int get(char s[]) { int i, j, now = 0, cnt = 0, len = 0; for (i=0; s[i]; ++i) { if (s[i] == '=') cnt++; else for (j=0; j<6; ++j) bits[++now] = ma[s[i]] & (1 << (5 - j)) ? 1 : 0; } len = i; now -= cnt * 2; len = now / 8; now = 0; int tlen = 0, tmp; for (i=0; i<len; ++i) { tmp = 0; for (j=0; j<8; ++j) tmp += bits[++now] * pwr[7-j]; ssss[tlen++] = tmp; } return tlen; } const int maxn = 50000; class AC { int next[maxn][256]; int fail[maxn]; int ed[maxn]; int last[maxn]; int qsz, root; inline int newnode() { int i; for (i=0; i<256; ++i) next[qsz][i] = 0; fail[qsz] = last[qsz] = ed[qsz] = 0; return qsz++; } public: void init() { qsz = 0; root = newnode(); } void insert(int s[], int len) { int i, id, rt = 0; for (i=0; i<len; ++i) { id = s[i]; if (!next[rt][id]) next[rt][id] = newnode(); rt = next[rt][id]; } ed[rt]++; } void build() { int i, rt, id; queue<int> q; rt = root; fail[root] = last[root] = rt = root; for (i=0; i<256; ++i) if (next[rt][i]) q.push(next[rt][i]); while (!q.empty()) { rt = q.front(); q.pop(); for (i=0; i<256; ++i) { if (!next[rt][i]) next[rt][i] = next[fail[rt]][i]; else { fail[next[rt][i]] = next[fail[rt]][i]; q.push(next[rt][i]); last[next[rt][i]] = ed[fail[next[rt][i]]] ? fail[next[rt][i]] : last[next[rt][i]]; } } } } int query(int s[], int len) { int rt = 0, tmp, i; int res = 0; set<int> vis; for (i=0; i<len; ++i) { tmp = next[rt][s[i]]; while (tmp) { if (ed[tmp]) { if (vis.find(tmp) == vis.end()) vis.insert(tmp); } tmp = last[tmp]; } rt = next[rt][s[i]]; } for (set<int>::iterator iter=vis.begin(); iter!=vis.end(); ++iter) res += ed[*iter]; return res; } }ac; int main() { // freopen("E:\\input.txt", "r", stdin); int i, n, len; bool flag = true; init(); while (~scanf("%d", &n)) { ac.init(); for (i=0; i<n; ++i) { scanf("%s", str); len = get(str); ac.insert(ssss, len); } ac.build(); scanf("%d", &n); for (i=0; i<n; ++i) { scanf("%s", str); len = get(str); printf("%d\n", ac.query(ssss, len)); } printf("\n"); } return 0; }
Solved 1 / 1 E POJ 2778 DNA Sequence 题意: 给定m个由ATGC组成的模式串, 问一个长度为n的由ATGC组成的串不含任何一个模式串的种类数. (0 <= m <= 10), |S|<10 n (1 <= n <=2000000000) 分析: 如果n比较小的情况下: 我们可以先建立AC自动机,然后建模式串的末尾节点标记, 同时他们的fail指针也进行标记.(被标记的节点说明是不可走的.) 然后我们从根节点出发,如果下个节点是标记过的节点, 那么说明是不可走的,我们就跳过他.然后继续走. 最后当我们走到长度为n时,我们的答案也出来了. ----> 我们在走的过程中累加答案, dp[length][state] 表示在长度为length,状态为state下的方案数. dp[length+1][nex_state] += dp[length][state]; 最后答案就是: sigma(dp[n][0...qsz]) , qsz是状态的总数. 但是,这道题的n非常大. 所以我们需要考虑一些加速的办法. n有2e9. 可以用矩阵快速幂来加速. 我们知道, 对于一个邻接矩阵A(离散数学上有,在矩阵那一章.), A^0表示走长度为0的方案数, A^1表示走长度为1的方案数, .... A^n表示走长度为n的发案数. 例如从点1出发到点10走n步 那么答案应该是 T = (A^n) ans = T[1][10]; 所以我们将我们可以走到的状态构建成一个邻接矩阵,然后快速幂. 最后答案就是sigma(A[0][0..qsz])
#include <cstdio> #include <queue> #include <cstring> using namespace std; typedef long long ll; const ll mod = 100000; class Matrix { public: int r, c; ll mat[128][128]; ll *operator [] (int x) { return mat[x]; } Matrix operator * (const Matrix &a) const { Matrix res; res.r = r; res.c = a.c; int i, j, k; for (i=0; i<res.r; ++i) { for (j=0; j<res.c; ++j) { res[i][j] = 0; for (k=0; k<c; ++k) res[i][j] = (res[i][j] + mat[i][k] * a.mat[k][j]) % mod; } } return res; } }mt; Matrix pwr(const Matrix &a, int k) { Matrix base = a, r; int i, j; r.r = a.r; r.c = a.c; for (i=0; i<r.r; ++i) for (j=0; j<r.c; ++j) r[i][j] = i==j; while (k) { if (k & 1) r = r * base; base = base * base; k >>= 1; } return r; } class AC { public: int next[256][4]; int ed[256]; int fail[256]; int qsz, root; inline int getid(char ch) { int res; switch (ch) { case 'A': res = 0; break; case 'C': res = 1; break; case 'G': res = 2; break; case 'T': res = 3; break; } return res; } inline int newnode() { int i; for (i=0; i<4; ++i) next[qsz][i] = 0; ed[qsz] = fail[qsz] = 0; return qsz++; } void init() { qsz = 0; root = newnode(); } void insert(char s[]) { int i, id, rt = 0, len = strlen(s); for (i=0; i<len; ++i) { id = getid(s[i]); if (!next[rt][id]) next[rt][id] = newnode(); rt = next[rt][id]; } ed[rt]++; } void build() { int rt, i; queue<int> q; rt = fail[root] = 0; for (i=0; i<4; ++i) { if (next[rt][i]) q.push(next[rt][i]); } while (!q.empty()) { rt = q.front(); q.pop(); if (ed[rt]) { } if (ed[fail[rt]]) ed[rt] = 1; for (i=0; i<4; ++i) { if (!next[rt][i]) next[rt][i] = next[fail[rt]][i]; else { fail[next[rt][i]] = next[fail[rt]][i]; q.push(next[rt][i]); } } } } void make() { memset(&mt, 0, sizeof(Matrix)); int i, j; mt.r = mt.c = qsz; for (i=0; i<qsz; ++i) { for (j=0; j<4; ++j) { if (ed[next[i][j]]) continue; mt[i][next[i][j]]++; } } } }ac; char str[16]; int main() { // freopen("E:\\input.txt", "r", stdin); int i, n, m; while (~scanf("%d%d", &m, &n)) { memset(&mt, 0, sizeof(mt)); ac.init(); for (i=1; i<=m; ++i) { scanf("%s", str); ac.insert(str); } ac.build(); ac.make(); mt = pwr(mt, n); int res = 0; for (i=0; i<ac.qsz; ++i) res = (res + mt[0][i] + mod) % mod; printf("%d\n", res); } return 0; }
Solved 1 / 1 F HDU 2243 考研路茫茫――单词情结 题意: 给定n个模式串,问长度为1...L中的串出现任一模式串的个数. 0<n<6 |S|<=5 0<L<2^31 分析: 从上一题我们知道如何求 不出现模式串 长度为n的串 有多少种. 而这个题是要我们求 出现任一个模式串的文本串的个数. 我们先考虑长度为n的串有多少种可能: 很明显 出现任一模式串的文本串个数 = 文本串种类数 - 没出现任一模式串的文本串个数. 所以我们就可以套用上一题的解法,把 没出现任一模式串的文本串个数 求出. 所以对于长度为n的,我们答案res_n已经求出了. 因此 文本串长度从1...n的答案就是 res = res_1 + res_2 + res_3 + ... + res_n; 但是, n非常大! 2^31, 如果只求一个,我们可以很快求出,如果求多个,我们复杂度得乘以一个n. 明显是不可行的. 但是,我们是通过矩阵实现的.所以我们先按照矩阵的写法把答案写出: Ans = A^1 + A^2 + A^3 + A^4 + ... + A^n; 我们现在的问题转化成了, 如何快速的求出 A^1 + A^2 + A^3 + A^4 + ... + A^n 这个式子了. 我们可以二分求和: 具体百度..或者看代码..一下就懂了. Matrix get(Matrix mat, int k) { if (k == 1) return mat; if (k & 1) return get(mat, k-1) + pwr(mat, k); else return (pwr(mat, k / 2) + pwr(mat, 0)) * get(mat, k / 2); // 注意对于矩阵,这儿用pwr(mat, 0)而不是把式子拆开. } (听说也可以在矩阵上添加改改然后可以求出.但是我不会.)
#include <cstdio> #include <queue> #include <cstring> #include <iostream> using namespace std; typedef unsigned long long ull; class Matrix { public: int r, c; ull mat[32][32]; ull *operator [] (int x) { return mat[x]; } Matrix operator * (const Matrix &a) const { Matrix res; res.r = r; res.c = a.c; int i, j, k; for (i=0; i<res.r; ++i) { for (j=0; j<res.c; ++j) { res[i][j] = 0; for (k=0; k<c; ++k) res[i][j] = (res[i][j] + mat[i][k] * a.mat[k][j]); } } return res; } Matrix operator + (const Matrix &a) const { Matrix res; res.r = r; res.c = a.c; int i, j; for (i=0; i<res.r; ++i) for (j=0; j<res.c; ++j) res[i][j] = (mat[i][j] + a.mat[i][j]); return res; } }mt; Matrix pwr(const Matrix &a, int k) { Matrix base = a, r; int i, j; r.r = a.r; r.c = a.c; for (i=0; i<r.r; ++i) for (j=0; j<r.c; ++j) r[i][j] = i==j; while (k) { if (k & 1) r = r * base; base = base * base; k >>= 1; } return r; } Matrix get(Matrix mat, int k) { if (k == 1) return mat; if (k & 1) return get(mat, k-1) + pwr(mat, k); else return (pwr(mat, k / 2) + pwr(mat, 0)) * get(mat, k / 2); } ull pwr4(ull k) { ull base = 26*1llu, r = 1; while (k) { if (k & 1) r *= base; base *= base; k >>= 1; } return r; } ull get(ull mat, ull k) { if (k == 1) return mat; if (k & 1) return get(mat, k-1) + pwr4(k); else return (pwr4(k / 2) + pwr4(0)) * get(mat, k / 2); } class AC { public: int next[128][26]; int ed[128]; int fail[128]; int qsz, root; inline int getid(char ch) { return ch - 'a'; } inline int newnode() { int i; for (i=0; i<26; ++i) next[qsz][i] = 0; ed[qsz] = fail[qsz] = 0; return qsz++; } void init() { qsz = 0; root = newnode(); } void insert(char s[]) { int i, id, rt = 0, len = strlen(s); for (i=0; i<len; ++i) { id = getid(s[i]); if (!next[rt][id]) next[rt][id] = newnode(); rt = next[rt][id]; } ed[rt]++; } void build() { int rt, i; queue<int> q; rt = fail[root] = 0; for (i=0; i<26; ++i) if (next[rt][i]) q.push(next[rt][i]); while (!q.empty()) { rt = q.front(); q.pop(); if (ed[fail[rt]]) ed[rt] = 1; for (i=0; i<26; ++i) { if (!next[rt][i]) next[rt][i] = next[fail[rt]][i]; else { fail[next[rt][i]] = next[fail[rt]][i]; q.push(next[rt][i]); } } } } void make() { memset(&mt, 0, sizeof(Matrix)); int i, j; mt.r = mt.c = qsz; for (i=0; i<qsz; ++i) { for (j=0; j<26; ++j) { if (ed[next[i][j]]) continue; mt[i][next[i][j]]++; } } } }ac; char str[16]; int main() { // freopen("E:\\input.txt", "r", stdin); int i, n; ull m; while (cin >> n >> m) { memset(&mt, 0, sizeof(mt)); ac.init(); for (i=1; i<=n; ++i) { scanf("%s", str); ac.insert(str); } ac.build(); ac.make(); mt = get(mt, m); ull res = get(26*1llu, m); for (i=0; i<ac.qsz; ++i) res -= mt[0][i]; cout << res << endl; } return 0; }
Solved 1 / 1 G POJ 1625 Censored! 题意: 有n个字符的字符集, p个禁止串(禁止出现的串), 问一个长度为m的串不含禁止串的种类数. 1 <= N <= 50 1 <= M <= 50 0 <= P <= 10 |S|<=10 分析: 我们发现和E题就是同一个题. 但是!!!! 这个题,n特别小. 所以,我们可以dp做,开心不?~~ 还有更开心的.这个题不用取模? 2333 真的太贴心了不用取模.....2333 什么意思呢? 你可以直接搞......才怪...你需要用大数.... 因为大数太耗内存了...用矩阵会炸,但是可以用dp做,参考上面E题的思路.然后套上大数板子就可以了.
#include <iostream> #include <sstream> #include <cstring> #include <cstdio> #include <queue> #include <map> using namespace std; typedef long long LL; #define MAX_L 90 class bign { public: int len, s[MAX_L]; bign(); bign(const char*); bign(int); bool sign; string toStr() const; friend istream& operator>>(istream &,bign &); friend ostream& operator<<(ostream &,bign &); bign operator=(const char*); bign operator=(int); bign operator=(const string); bool operator>(const bign &) const; bool operator>=(const bign &) const; bool operator<(const bign &) const; bool operator<=(const bign &) const; bool operator==(const bign &) const; bool operator!=(const bign &) const; bign operator+(const bign &) const; bign operator++(); bign operator++(int); bign operator+=(const bign&); bign operator-(const bign &) const; bign operator--(); bign operator--(int); bign operator-=(const bign&); bign operator*(const bign &)const; bign operator*(const int num)const; bign operator*=(const bign&); bign operator/(const bign&)const; bign operator/=(const bign&); bign operator%(const bign&)const; bign factorial()const; bign Sqrt()const; bign pow(const bign&)const; void clean(); ~bign(); }; #define max(a,b) a>b ? a : b #define min(a,b) a<b ? a : b bign::bign() { memset(s, 0, sizeof(s)); len = 1; sign = 1; } bign::bign(const char *num) { *this = num; } bign::bign(int num) { *this = num; } string bign::toStr() const { string res; res = ""; for (int i = 0; i < len; i++) res = (char)(s[i] + '0') + res; if (res == "") res = "0"; if (!sign&&res != "0") res = "-" + res; return res; } istream &operator>>(istream &in, bign &num) { string str; in>>str; num=str; return in; } ostream &operator<<(ostream &out, bign &num) { out<<num.toStr(); return out; } bign bign::operator=(const char *num) { memset(s, 0, sizeof(s)); char a[MAX_L] = ""; if (num[0] != '-') strcpy(a, num); else for (int i = 1, tlen = strlen(num); i < tlen; i++) a[i - 1] = num[i]; sign = !(num[0] == '-'); len = strlen(a); for (int i = 0, tlen = strlen(a); i < tlen; i++) s[i] = a[len - i - 1] - 48; return *this; } bign bign::operator=(int num) { char temp[MAX_L]; sprintf(temp, "%d", num); *this = temp; return *this; } bign bign::operator=(const string num) { const char *tmp; tmp = num.c_str(); *this = tmp; return *this; } bool bign::operator<(const bign &num) const { if (sign^num.sign) return num.sign; if (len != num.len) return len < num.len; for (int i = len - 1; i >= 0; i--) if (s[i] != num.s[i]) return sign ? (s[i] < num.s[i]) : (!(s[i] < num.s[i])); return !sign; } bool bign::operator>(const bign&num)const { return num < *this; } bool bign::operator<=(const bign&num)const { return !(*this>num); } bool bign::operator>=(const bign&num)const { return !(*this<num); } bool bign::operator!=(const bign&num)const { return *this > num || *this < num; } bool bign::operator==(const bign&num)const { return !(num != *this); } bign bign::operator+(const bign &num) const { if (sign^num.sign) { bign tmp = sign ? num : *this; tmp.sign = 1; return sign ? *this - tmp : num - tmp; } bign result; result.len = 0; int temp = 0; for (int i = 0; temp || i < (max(len, num.len)); i++) { int t = s[i] + num.s[i] + temp; result.s[result.len++] = t % 10; temp = t / 10; } result.sign = sign; return result; } bign bign::operator++() { *this = *this + 1; return *this; } bign bign::operator++(int) { bign old = *this; ++(*this); return old; } bign bign::operator+=(const bign &num) { *this = *this + num; return *this; } bign bign::operator-(const bign &num) const { bign b=num,a=*this; if (!num.sign && !sign) { b.sign=1; a.sign=1; return b-a; } if (!b.sign) { b.sign=1; return a+b; } if (!a.sign) { a.sign=1; b=bign(0)-(a+b); return b; } if (a<b) { bign c=(b-a); c.sign=false; return c; } bign result; result.len = 0; for (int i = 0, g = 0; i < a.len; i++) { int x = a.s[i] - g; if (i < b.len) x -= b.s[i]; if (x >= 0) g = 0; else { g = 1; x += 10; } result.s[result.len++] = x; } result.clean(); return result; } bign bign::operator * (const bign &num)const { bign result; result.len = len + num.len; for (int i = 0; i < len; i++) for (int j = 0; j < num.len; j++) result.s[i + j] += s[i] * num.s[j]; for (int i = 0; i < result.len; i++) { result.s[i + 1] += result.s[i] / 10; result.s[i] %= 10; } result.clean(); result.sign = !(sign^num.sign); return result; } bign bign::operator*(const int num)const { bign x = num; bign z = *this; return x*z; } bign bign::operator*=(const bign&num) { *this = *this * num; return *this; } bign bign::operator /(const bign&num)const { bign ans; ans.len = len - num.len + 1; if (ans.len < 0) { ans.len = 1; return ans; } bign divisor = *this, divid = num; divisor.sign = divid.sign = 1; int k = ans.len - 1; int j = len - 1; while (k >= 0) { while (divisor.s[j] == 0) j--; if (k > j) k = j; char z[MAX_L]; memset(z, 0, sizeof(z)); for (int i = j; i >= k; i--) z[j - i] = divisor.s[i] + '0'; bign dividend = z; if (dividend < divid) { k--; continue; } int key = 0; while (divid*key <= dividend) key++; key--; ans.s[k] = key; bign temp = divid*key; for (int i = 0; i < k; i++) temp = temp * 10; divisor = divisor - temp; k--; } ans.clean(); ans.sign = !(sign^num.sign); return ans; } bign bign::operator/=(const bign&num) { *this = *this / num; return *this; } bign bign::operator%(const bign& num)const { bign a = *this, b = num; a.sign = b.sign = 1; bign result, temp = a / b*b; result = a - temp; result.sign = sign; return result; } bign bign::pow(const bign& num)const { bign result = 1; for (bign i = 0; i < num; i++) result = result*(*this); return result; } bign bign::factorial()const { bign result = 1; for (bign i = 1; i <= *this; i++) result *= i; return result; } void bign::clean() { if (len == 0) len++; while (len > 1 && s[len - 1] == '\0') len--; } bign bign::Sqrt()const { if(*this<0)return -1; if(*this<=1)return *this; bign l=0,r=*this,mid; while(r-l>1) { mid=(l+r)/2; if(mid*mid>*this) r=mid; else l=mid; } return l; } bign::~bign() { } bign pwr4(bign base, int k) { bign r = 1; while (k) { if (k & 1) r = r * base; base = base * base; k >>= 1; } return r; } map<int, int> ma; class AC { public: int next[128][64]; int fail[128]; int ed[128]; int qsz, root, qn; inline int newnode() { int i; for (i=0; i<qn; ++i) next[qsz][i] = 0; fail[qsz] = ed[qsz] = 0; return qsz++; } void init(int n) { qsz = 0; qn = n; root = newnode(); } void insert(string s) { int i, rt = 0, id; for (i=0; s[i]; ++i) { id = ma[s[i]]; if (!next[rt][id]) next[rt][id] = newnode(); rt = next[rt][id]; } ed[rt]++; } void build() { int i, rt; queue<int> q; rt = fail[root] = 0; for (i=0; i<qn; ++i) if (next[rt][i]) q.push(next[rt][i]); while (!q.empty()) { rt = q.front(); q.pop(); if (ed[fail[rt]]) ed[rt] = 1; for (i=0; i<qn; ++i) { if (!next[rt][i]) next[rt][i] = next[fail[rt]][i]; else { fail[next[rt][i]] = next[fail[rt]][i]; q.push(next[rt][i]); } } } } }ac; bign dp[128][128]; int main() { // freopen("E:\\input.txt", "r", stdin); int n, m, p, i, j, k; char str[64]; while (cin >> n >> m >> p) { getchar(); ma.clear(); ac.init(n); gets(str); for (i=0; i<n; ++i) ma[str[i]] = i; for (i=0; i<p; ++i) { gets(str); ac.insert(str); } ac.build(); int rt; for (i=0; i<=m; ++i) for (j=0; j<ac.qsz; ++j) dp[i][j] = 0; dp[0][0] = 1; for (i=0; i<m; ++i) { for (j=0; j<ac.qsz; ++j) { for (k=0; k<n; ++k) { if (ac.ed[ac.next[j][k]]) continue; dp[i+1][ac.next[j][k]] = dp[i+1][ac.next[j][k]] + dp[i][j]; } } } bign res = 0; for (i=0; i<ac.qsz; ++i) res = res + dp[m][i]; cout << res << endl; } return 0; }
Solved 1 / 5 H HDU 2825 Wireless Password 题意: 问一个长度为n的密码可能有多少种,条件: 1. 给定m个可能是密码的子串 2. 密码至少含k个可能的子串. 1<=n<=25 0<=m<=10 |S|<=10 分析: n比较小,我们考虑一下dp. k也是1...10的,所以我们可以有状压. 我们参考E G题的dp思路, 不难写出dp[length][state][k] length表示当前长度, state表示的是在AC自动机的状态,k是选取密码串的状态. 然后我们状态就好转移了: dp[length+1][nex_state][nex_k] += dp[length][state][k]; 最后答案是 sigma(dp[n][1..qsz][num>=k])
#include <cstdio> #include <queue> #include <cstring> using namespace std; const int mod = 20090717; typedef int ll; ll dp[32][128][1024]; int num[1024]; class AC { public: int next[128][26]; int fail[128]; int ed[128]; int qsz, root; int newnode() { int i; for (i=0; i<26; ++i) next[qsz][i] = 0; fail[qsz] = ed[qsz] = 0; return qsz++; } inline int getid(char ch) { return ch - 'a'; } init() { qsz = 0; root = newnode(); } void insert(char s[], int tid) { int i, rt = 0, id; for (i=0; s[i]; ++i) { id = getid(s[i]); if (!next[rt][id]) next[rt][id] = newnode(); rt = next[rt][id]; } ed[rt] |= (1 << tid); } void build() { int i, rt; queue<int> q; rt = root; for (i=0; i<26; ++i) if (next[rt][i]) q.push(next[rt][i]); while (!q.empty()) { rt = q.front(); q.pop(); if (ed[fail[rt]]) ed[rt] |= ed[fail[rt]]; for (i=0; i<26; ++i) { if (!next[rt][i]) next[rt][i] = next[fail[rt]][i]; else { fail[next[rt][i]] = next[fail[rt]][i]; q.push(next[rt][i]); } } } } ll get(int n, int m, int kk) { int i, j, k, d, z, st; memset(dp, 0, sizeof(dp)); dp[0][0][0] = 1; for (z=0; z<(1<<m); ++z) { for (i=0; i<n; ++i) { for (j=0; j<qsz; ++j) { if (!dp[i][j][z]) continue; for (d=0; d<26; ++d) { st = z | ed[next[j][d]]; dp[i+1][next[j][d]][st] = (dp[i+1][next[j][d]][st] + dp[i][j][z]) % mod; } } } } ll res = 0; for (z=0; z<(1<<m); ++z) if (num[z] < kk) continue; else for (i=0; i<qsz; ++i) res = (res + dp[n][i][z]) % mod; return res; } }ac; char str[32]; int main() { int i, j, n, m, k, tmp, cnt; for (i=0; i<1024; ++i) { tmp = i; cnt = 0; while ((tmp & -tmp)) { tmp -= (tmp & -tmp); cnt++; } num[i] = cnt; } while (~scanf("%d%d%d", &n, &m, &k) && (n || m || k)) { ac.init(); for (i=0; i<m; ++i) { scanf("%s", str); ac.insert(str, i); } ac.build(); ll res; res = ac.get(n, m, k); printf("%d\n", res); } return 0; }
Solved 1 / 3 I HDU 2296 Ring 题意: 给定m个单词,每个单词都有价值,求长度为n的最大价值的单词. 如果有多个答案,取长度较小的,如果长度相同,则取字典序较小的. 0 < N ≤ 50 0 < M ≤ 100 |S|<=10 分析: 单词的最大值好求,dp,问题是记录路径. ...很暴力,我们直接用string存,然后比较就OK了. dp[length][state]
#include <algorithm> #include <cstring> #include <cstdio> #include <queue> #include <string> using namespace std; int val[128]; char ans[5056][128]; class AC { public: int next[5056][26]; int fail[5056]; int rcd[5056]; int ed[5056]; int qsz; int newnode() { int i; for (i=0; i<26; ++i) next[qsz][i] = 0; fail[qsz] = ed[qsz] = 0; return qsz++; } void init() { qsz = 0; newnode(); } inline int getid(char ch) { return ch-'a'; } void insert(char s[], char tid) { int i, id, rt = 0; for (i=0; s[i]; ++i) { id = getid(s[i]); if (!next[rt][id]) next[rt][id] = newnode(); rt = next[rt][id]; } rcd[tid] = rt; } void build() { int rt, i; queue<int> q; rt = 0; for (i=0; i<26; ++i) if (next[rt][i]) q.push(next[rt][i]); while (!q.empty()) { rt = q.front(); q.pop(); ed[rt] += ed[fail[rt]]; for (i=0; i<26; ++i) { if (!next[rt][i]) next[rt][i] = next[fail[rt]][i]; else { fail[next[rt][i]] = next[fail[rt]][i]; q.push(next[rt][i]); } } } } int dp[64][5056]; string ss[64][5056]; void slove(int n) { int i, j, k; for (i=0; i<=n; ++i) for(j=0; j<qsz; ++j) ss[i][j].clear(); memset(dp, -1, sizeof(dp)); dp[0][0] = 0; int nex, tmp; for (i=0; i<n; ++i) { for (j=0; j<qsz; ++j) { if (dp[i][j] == -1) continue; for (k=0; k<26; ++k) { nex = next[j][k]; tmp = ed[nex] + dp[i][j]; if (dp[i+1][nex] == tmp) { if (ss[i+1][nex] > ss[i][j] + char(k + 'a')) { ss[i+1][nex] = ss[i][j] + char(k + 'a'); } } else if (dp[i+1][nex] < tmp){ dp[i+1][nex] = tmp; ss[i+1][nex] = ss[i][j] + char(k + 'a'); } } } } string ans; int ians = 0; for (i=n; i>=0; --i) { for (j=0; j<qsz; ++j) { if (dp[i][j] > ians) { ians = dp[i][j]; ans = ss[i][j]; } else if (dp[i][j] == ians) { if (ss[i][j].size() < ans.size()) ans = ss[i][j]; else if (ss[i][j].size() == ans.size()) { if (ss[i][j] < ans) ans = ss[i][j]; } } } } if (ans.size() == 0) printf("\n"); else printf("%s\n", ans.c_str()); } }ac; int main() { // freopen("E:\\input.txt", "r", stdin); int t, n, m, i, tmp; char str[64]; scanf("%d", &t); while (t--) { scanf("%d%d", &n, &m); ac.init(); for (i=1; i<=m; ++i) { scanf("%s", str); ac.insert(str, i); } for (i=1; i<=m; ++i) { scanf("%d", &tmp); ac.ed[ac.rcd[i]] += tmp; } ac.build(); ac.slove(n); } return 0; }
Solved 1 / 5 J HDU 2457 DNA repair 题意: 给你n个模式串,一个文本串,然后可以让文本串的某一个字母修改. 问至少要修改多少次,才不会出现任何一个模式串. 分析: 假设我们的状态, dp[length][state]表示在长度为length,state状态时的最小值. 然后我们可以跟着文本串走就OK了: 即 if (走到下一个状态,经过原字符串的字符) tmp = dp[length][state]; else tmp = dp[length][state] + 1; 然后 dp[length+1][nex_state] = min(dp[length+1][nex_state], tmp);
#include <cstdio> #include <cstring> #include <queue> #include <algorithm> using namespace std; class AC { public: int next[1024][4]; int fail[1024]; bool ed[1024]; int qsz, root; inline int getid(char ch) { int res; switch (ch) { case 'A': res=0; break; case 'T': res=1; break; case 'G': res=2; break; case 'C': res=3; break; } return res; } int newnode() { int i; for (i=0; i<4; ++i) next[qsz][i] = 0; fail[qsz] = ed[qsz] = false; return qsz++; } void init() { qsz = 0; root = newnode(); } void insert(char s[]) { int i, rt = 0, id; for (i=0; s[i]; ++i) { id = getid(s[i]); if (!next[rt][id]) next[rt][id] = newnode(); rt = next[rt][id]; } ed[rt] = true; } void build() { int rt, i; queue<int> q; rt = root; for (i=0; i<4; ++i) if (next[rt][i]) q.push(next[rt][i]); while (!q.empty()) { rt = q.front(); q.pop(); ed[rt] |= ed[fail[rt]]; for (i=0; i<4; ++i) { if (!next[rt][i]) next[rt][i] = next[fail[rt]][i]; else { fail[next[rt][i]] = next[fail[rt]][i]; q.push(next[rt][i]); } } } } int dp[1024][1024]; int getans(char s[]) { int res = 0x3f3f3f3f; int i, j, k, tmp; memset(dp, 0x3f, sizeof(dp)); dp[0][0] = 0; for (i=0; s[i]; ++i) { for (j=0; j<qsz; ++j) { if (ed[j]) continue; for (k=0; k<4; ++k) { if (ed[next[j][k]]) continue; if (getid(s[i]) == k) tmp = dp[i][j]; else tmp = dp[i][j] + 1; dp[i+1][next[j][k]] = min(dp[i+1][next[j][k]], tmp); } } } for (j=0; j<qsz; ++j) res = min(res, dp[i][j]); return res == 0x3f3f3f3f ? -1 : res; } }ac; char str[1024]; int main() { // freopen("E:\\input.txt", "r", stdin); int i, n, cas = 1; while (~scanf("%d", &n) && n) { ac.init(); for (i=0; i<n; ++i) { scanf("%s", str); ac.insert(str); } ac.build(); scanf("%s", str); printf("Case %d: %d\n", cas++, ac.getans(str)); } return 0; }
Solved 1 / 12 K ZOJ 3228 Searching the String 题意: 给n个模式串, 然后查询模式串出现的次数. 有两种类型的查询: 0 支持重叠. 1 不支持重叠. |S|<6 |S|<10^5 分析: 裸的AC自动机题, 多于不重叠的,我们可以用一个lst[maxn]数组和deep[id]数组来维护. deep[state] = i ---> 在建立Trie树的时候维护,表示字符长度. lst[state]表示当前状态上一次出现的位置,在查询的时候更新. 如果i-lst[state]>=deep[state]说明不重叠,可以取值,同时更新lst[state]; .....不过这个题有个恶心的坑点: 他给的模式串可能一样!!即会给出多次.所以记录答案的时候要注意 (建Trie树的时候,维护同一个节点需要保存2个或者多个串答案的可能.)
#include <cstdio> #include <queue> #include <cstring> #include <set> using namespace std; const int maxn = 1e5+50; const int cmaxn = maxn * 6; int ans[maxn]; char ss[cmaxn]; int fa[maxn]; class AC{ public: int next[cmaxn][26]; int fail[cmaxn]; int ed[cmaxn][2]; int lst[cmaxn]; int deep[cmaxn]; int qsz, root; inline int getid(char ch) { return ch-'a'; } inline int newnode() { int i; for (i=0; i<26; ++i) next[qsz][i] = 0; deep[qsz] = fail[qsz] = ed[qsz][0] = ed[qsz][1] = 0; return qsz++; } void init() { qsz = 0; root = newnode(); memset(fa, 0, sizeof(fa)); } void insert(char s[], int tid, int op) { int i, id, rt = 0, len = strlen(s); for (i=0; i<len; ++i) { id = getid(s[i]); if (!next[rt][id]) next[rt][id] = newnode(); rt = next[rt][id]; deep[rt] = i+1; } if (!ed[rt][op]) { ed[rt][op] = tid; } fa[tid] = ed[rt][op]; } void build() { queue<int> q; int rt, i; fail[root] = rt = root; for (i=0; i<26; ++i) if (next[rt][i]) q.push(next[rt][i]); while (!q.empty()) { rt = q.front(); q.pop(); for (i=0; i<26; ++i) { if (!next[rt][i]) next[rt][i] = next[fail[rt]][i]; else { fail[next[rt][i]] = next[fail[rt]][i]; q.push(next[rt][i]); } } } } int query(char s[]) { int rt = 0, tmp, i, len = strlen(s); memset(lst, -1, sizeof(lst)); for (i=0; i<len; ++i) { tmp = rt = next[rt][getid(s[i])]; while (tmp) { if (lst[tmp]==-1 || i-lst[tmp] >= deep[tmp]) { ans[ed[tmp][1]]++; lst[tmp] = i; } ans[ed[tmp][0]]++; tmp = fail[tmp]; } } } }ac; int main() { // freopen("E:\\input.txt", "r", stdin); int n, i, op, cas=1; char str[128]; while (~scanf("%s", ss)) { scanf("%d", &n); memset(ans, 0, sizeof(ans)); ac.init(); for (i=1; i<=n; ++i) { scanf("%d%s", &op, str); ac.insert(str, i, op); } ac.build(); ac.query(ss); printf("Case %d\n", cas++); for (i=1; i<=n; ++i) printf("%d\n", ans[i] ? ans[i] : ans[fa[i]]); printf("\n"); } return 0; }
Solved 1 / 2 L HDU 3341 Lost's revenge 题意: (都只含ATGC)给n个模式串,一个文本串,文本串字母顺序可以调整.问调整后,最多可以出现几个模式串.(可以重叠) 1<=N<=50 |S|<=10 |S| <= 40 分析: 如果是任意的长度为m的字符串,我们可以直接dp搞. 但是现在多了一些限制: 长度为m,同时ATGC的个数是固定的了. 所以,我们不妨令dp[length][state][num_A][num_T][num_G][num_C]来表示我们当前的状态可以获取的最大值. length表示当前长度, state表示的是在AC自动机的状态, num表示用了多少个. 所以,我们的状态可以转移了. 但是,我们发现,6维吓人了..开不下. 不过,我们发现length这一维可以剩,因为他和num那4维是等价的. 所以,我们现在有一个5维的dp num的范围是40,state是500 500*40^4还是开不下. 考虑状态压缩,我们可以把num那四维压缩. 我们可以乘以权值来压缩,这样可以保证不会重叠...然后最多只有30000多(我证不来...别人题解说的 2333) 权值: _h[0] = 1; _h[1] = (num[0] + 1); _h[2] = (num[0] + 1) * (num[1] + 1); _h[3] = (num[0] + 1) * (num[1] + 1) * (num[2] + 1); 然后我们的dp[state][tt], 只有两维了..
#include <cstdio> #include <cstring> #include <queue> #include <algorithm> using namespace std; class AC { public: int next[528][4]; int fail[528]; int ed[528]; int qsz, root; // ATGC inline int getid(char ch) { switch (ch) { case 'A': return 0; case 'T': return 1; case 'G': return 2; case 'C': return 3; } } inline int newnode() { int i; for (i=0; i<4; ++i) next[qsz][i] = 0; ed[qsz] = fail[qsz] = 0; return qsz++; } void init() { qsz = 0; root = newnode(); } void insert(char s[]) { int i, id, rt = 0; for (i=0; s[i]; ++i) { id = getid(s[i]); if (!next[rt][id]) next[rt][id] = newnode(); rt = next[rt][id]; } ed[rt]++; } void build() { int i, rt; queue<int> q; rt = 0; for (i=0; i<4; ++i) if (next[rt][i]) q.push(next[rt][i]); while (!q.empty()) { rt = q.front(); q.pop(); ed[rt] += ed[fail[rt]]; for (i=0; i<4; ++i) { if (!next[rt][i]) next[rt][i] = next[fail[rt]][i]; else { fail[next[rt][i]] = next[fail[rt]][i]; q.push(next[rt][i]); } } } } // ATGC int num[4]; int _h[4]; int dp[528][15555]; int slove(char s[]) { int i, len; num[0] = num[1] = num[2] = num[3] = 0; for (len=0; s[len]; ++len) num[getid(s[len])]++; _h[0] = 1; _h[1] = (num[0] + 1); _h[2] = (num[0] + 1) * (num[1] + 1); _h[3] = (num[0] + 1) * (num[1] + 1) * (num[2] + 1); memset(dp, -1, sizeof(dp)); dp[0][0] = 0; int A, T, G, C, k, od, ne; for (A=0; A<=num[0]; ++A) for (T=0; T<=num[1]; ++T) for (G=0; G<=num[2]; ++G) for (C=0; C<=num[3]; ++C) for (i=0; i<qsz; ++i) { od = A * _h[0] + T * _h[1] + G * _h[2] + C * _h[3]; if (dp[i][od] == -1) continue; for (k=0; k<4; ++k) { if (k==0 && A==num[0]) continue; if (k==1 && T==num[1]) continue; if (k==2 && G==num[2]) continue; if (k==3 && C==num[3]) continue; switch (k) { case 0: ne = od + _h[0]; break; case 1: ne = od + _h[1]; break; case 2: ne = od + _h[2]; break; case 3: ne = od + _h[3]; break; } dp[next[i][k]][ne] = max(dp[i][od]+ed[next[i][k]], dp[next[i][k]][ne]); } } int res = 0; od = num[0] * _h[0] + num[1] * _h[1] + num[2] * _h[2] + num[3] * _h[3]; for (i=0; i<qsz; ++i) res = max(res, dp[i][od]); return res; } }ac; char str[64]; int main() { // freopen("E:\\input.txt", "r", stdin); int n, i, cas = 1; while (scanf("%d", &n) && n) { ac.init(); for (i=0; i<n; ++i) { scanf("%s", str); ac.insert(str); } ac.build(); scanf("%s", str); printf("Case %d: %d\n", cas++, ac.slove(str)); } return 0; }
Solved 1 / 2 M HDU 3247 Resource Archiver 题意: 给n个串和m个串,问n个串组成一个最短的新串,不含任一个m串 (2 <= n <= 10, 1 <= m <= 1000) |S|<=1000 分析: 我们定义dp[i][state]表示节点i到达state这个状态的最小值,
state状态是选取n个串的情况,即状态压缩的情况,更新就行了.
dist[i][j]表示从i个串到j个串的最短距离.更新dp[][]需要用到.
dist[i][j]可以通过在AC自动机上bfs求得.
#include <cstdio> #include <cstring> #include <queue> using namespace std; const int maxn = 60500; int n, m; class AC { public: int next[maxn][2]; int fail[maxn]; int good[maxn]; int bad[maxn]; int qsz, root, cnt; int pos[256]; inline int getid(char ch) { return ch-'0'; } inline int newnode() { next[qsz][0] = next[qsz][1] = good[qsz] = bad[qsz] = fail[qsz] = 0; return qsz++; } init() { qsz = 0; cnt = 1; root = newnode(); } void insert(char s[], int tid) { int i, id, rt = root; for (i=0; s[i]; ++i) { id = getid(s[i]); if (!next[rt][id]) next[rt][id] = newnode(); rt = next[rt][id]; } pos[0] = 0; if (tid >= 0) good[rt] |= (1 << tid), pos[cnt++] = rt; else bad[rt] = 1; } void build() { int i, rt = root; queue<int> q; for (i=0; i<2; ++i) if (next[rt][i]) q.push(next[rt][i]); while (!q.empty()) { rt = q.front(); q.pop(); good[rt] |= good[fail[rt]]; bad[rt] |= bad[fail[rt]]; for (i=0; i<2; ++i) { if (!next[rt][i]) next[rt][i] = next[fail[rt]][i]; else { fail[next[rt][i]] = next[fail[rt]][i]; q.push(next[rt][i]); } } } } static const int inf = 0x3f3f3f3f; int dis[maxn]; int path[256][256]; void spfa(int st) { // 其实这只是一个bfs,找最短路. 在AC自动机上bfs,找到两个单词的最短路. queue<int> q; int i, rt, nex; q.push(pos[st]); memset(dis, 0x3f, sizeof(dis)); dis[pos[st]] = 0; while (!q.empty()) { rt = q.front(); q.pop(); for (i=0; i<2; ++i) { nex = next[rt][i]; if (bad[nex]) continue; if (dis[nex] == inf) { dis[nex] = dis[rt] + 1; q.push(nex); } } } for (i=0; i<cnt; ++i) path[st][i] = dis[pos[i]]; } int dp[256][1024]; int getans() { int i, tps, j, k, nex, state; /* cnt = 0; //获取所有需要连接上去的串的尾节点. 包括根节点. 因为是从根节点开始走的,所以需要包括根节点. pos[cnt++] = 0; for (i=0; i<qsz; ++i) if (good[i] && !bad[i]) pos[cnt++] = i;*/ // printf("%d \n", cnt); for (i=0; i<cnt; ++i) spfa(i); memset(dp, 0x3f, sizeof(dp)); // for (i=0; i<n; ++i) // 是以0为起点,这个初始化时以任一节点为起点的. // dp[i][(1<<i)] = 0; dp[0][0] = 0; tps = (1 << n) - 1; for (state=0; state<=tps; ++state) { for (i=0; i<cnt; ++i) { // if ((state & (1 << i))) continue; 只能走一次. // if (dp[i][state] == inf) continue; for (j=0; j<cnt; ++j) { // if ((state & (1 << j))) continue; 表示只能走一次. 但实际上可以走多次. nex = state | good[pos[j]]; dp[j][nex] = min(dp[j][nex], dp[i][state] + path[i][j]); } } } int res = inf; for (i=0; i<cnt; ++i) res = min(dp[i][tps], res); return res; } }ac; char str[1024]; int main() { int i; // freopen("E:\\input.txt", "r", stdin); while (scanf("%d%d", &n, &m) && (n || m)) { ac.init(); for (i=0; i<n; ++i) { scanf("%s", str); ac.insert(str, i); } for (i=0; i<m; ++i) { scanf("%s", str); ac.insert(str, -1); } ac.build(); printf("%d\n", ac.getans()); } return 0; }
然后这是一个类似的题,不过这个题要求只能走一遍.
https://vjudge.net/problem/HDU-4856
Solved 1 / 6 N ZOJ 3494 BCD Code 题意: 给你BCD编码: 1-9都对应一个4为的二进制,然后给定n个禁止出现的二进制 数字对应转化后不能出现这些二进制,然后问区间内有多个满足数. 分析: 关于区间内有多少个满足条件的数,一般我们直接考虑数位dp. 然后在数位dp中,状态是否可以继续走下去,如何走下去,结束的条件是什么,这是需要我们考虑的. 对于状态,我们发现那是路径,连续的状态,所以我们可以用AC自动机来构建. 然后在dfs过程中把数字拆成二进制,同时状态进行更新后传递下去. 然后...其实就是个板子题了.. 然后注意 A ≤ B < 10^200 所以读入要用字符串,然后对于左边区间要减一,也是需要处理的.
#include <queue> #include <cstdio> #include <cstring> using namespace std; typedef long long ll; const int mod = 1000000009; class AC { public: int next[2048][2]; int fail[2048]; bool ed[2048]; int qsz, root; inline int getid(char ch) { return ch-'0'; } inline int newnode() { next[qsz][0] = next[qsz][1] = fail[qsz] = ed[qsz] = 0; return qsz++; } void init() { qsz = 0; root = newnode(); } void insert(char s[]) { int i, id, rt = root; for (i=0; s[i]; ++i) { id = getid(s[i]); if (!next[rt][id]) next[rt][id] = newnode(); rt = next[rt][id]; } ed[rt] = true; } void build() { int i, rt = root; queue<int> q; if (next[rt][0]) q.push(next[rt][0]); if (next[rt][1]) q.push(next[rt][1]); while (!q.empty()) { rt = q.front(); q.pop(); ed[rt] |= ed[fail[rt]]; for (i=0; i<2; ++i) { if (!next[rt][i]) next[rt][i] = next[fail[rt]][i]; else { fail[next[rt][i]] = next[fail[rt]][i]; q.push(next[rt][i]); } } } } }ac; char str[32], left[256], right[256]; int digit[256]; int dp[256][2048]; ll dfs(int deep, int state, bool zero, bool lmt) { if (!deep) return !zero; if (!lmt && !zero && dp[deep][state]>=0) return dp[deep][state]; int i, j, nex, up = lmt ? digit[deep] : 9; ll res = 0; for (i=0; i<=up; ++i) { if (zero && i==0) res = (dfs(deep-1, 0, true, lmt && i==up) + res) % mod; else { nex = state; for (j=8; j; j>>=1) { if (i & j) nex = ac.next[nex][1]; else nex = ac.next[nex][0]; if (ac.ed[nex]) break; } if (ac.ed[nex]) continue; res = (dfs(deep-1, nex, false, lmt && i==up) + res) % mod; } } return (lmt || zero) ? res : dp[deep][state] = res; } ll cal(char s[], bool isleft) { int i, k, j, t, len, tt; tt = len = strlen(s); if (isleft) { tt--; while (s[tt] == '0') s[tt--] = '9'; s[tt]-=1; } if (tt == 0 && s[0]=='0') tt = 1; else tt = 0; for (k=0, i=tt; i<len; ++i) { digit[++k] = s[i] - '0'; } for (i=k,j=1; j<=k/2; ++j,--i) { t = digit[i]; digit[i] = digit[j]; digit[j] = t; } return dfs(k, 0, true, true); } int main() { // freopen("E:\\input.txt", "r", stdin); int t, n, i; scanf("%d", &t); while (t--) { memset(dp, -1, sizeof(dp)); ac.init(); scanf("%d", &n); for (i=0; i<n; ++i) { scanf("%s", str); ac.insert(str); } ac.build(); scanf("%s%s", left, right); // printf("%s %s\n", left, right); printf("%lld\n", (cal(right, false) - cal(left, true) + mod) % mod); } return 0; }
Solved 1 / 6 O HDU 4511 小明系列故事――女友的考验 题意: 要从点1走到点n,其中有些路径不能走.问最短距离.而且每次走点都是得递增走的. 分析: 最短路,我们可以考虑一下floyd等算法,我们发现,如果除掉"有些路径不能走"这个限制条件. 那么这个题就没意思了. emmm, 那么现在的问题是,我们如何维护下一个状态可不可以走呢? 路径emmm,用AC自动机来构建我们状态的图吧. 然后从第一个点出发(注意是从第一个点出发,而不是我们平时从根节点出发). dp[state][i]表示从state状态到第i个点的最小花费. 所以我们有 dp[nex_state][j] = min(dp[nex_state][j], dp[state][i]+dist[i][j])
#include <cmath> #include <queue> #include <cstdio> #include <cstring> using namespace std; struct Point { double x; double y; Point () { } Point (int xx, int yy) : x(xx), y(yy) { } double dist(Point &pt) { return sqrt((x - pt.x) * (x - pt.x) + (y - pt.y) * (y - pt.y)); } }pt[64]; double dist[64][64]; class AC { public: int next[512][64]; int fail[512]; bool bad[512]; int qsz, root, n; // R 1 D 0 inline int getid (char ch) { return ch=='R'; } inline int newnode() { int i; for (i=0; i<n; ++i) next[qsz][i] = 0; bad[qsz] = fail[qsz] = 0; return qsz++; } init(int n) { qsz = 0; this->n = n; root = newnode(); } void insert(int k) { int i, id, rt = root; for (i=0; i<k; ++i) { scanf("%d", &id); id--; if (!next[rt][id]) next[rt][id] = newnode(); rt = next[rt][id]; } bad[rt] = true; } void build() { queue<int> q; int i, rt = root; for (i=0; i<n; ++i) if (next[rt][i]) q.push(next[rt][i]); while (!q.empty()) { rt = q.front(); q.pop(); bad[rt] |= bad[fail[rt]]; for (i=0; i<n; ++i) { if (!next[rt][i]) next[rt][i] = next[fail[rt]][i]; else { fail[next[rt][i]] = next[fail[rt]][i]; q.push(next[rt][i]); } } } } const double INF = (1LL << 60); double dp[64][512]; void slove() { int i, j, k, nex, z; for (i=0; i<n; ++i) for (j=0; j<qsz; ++j) dp[i][j] = INF; dp[0][next[root][0]] = 0.0; for (i=0; i<n; ++i) for (z=0; z<qsz; ++z) { if (bad[z]) continue; for (j=i+1; j<n; ++j) { nex = next[z][j]; if (bad[nex]) { continue; } dp[j][nex] = min(dp[j][nex], dp[i][z] + dist[i][j]); } } double res = INF; for (i=0; i<qsz; ++i) if (!bad[i]) res = min(res, dp[n-1][i]); if (res == INF) printf("Can not be reached!\n"); else printf("%.2f\n", res); } }ac; int main() { int n, m, i, j, k; while (scanf("%d%d", &n, &m) && (n || m)) { ac.init(n); for (i=0; i<n; ++i) scanf("%lf%lf", &pt[i].x, &pt[i].y); for (i=0; i<n; ++i) for (j=0; j<n; ++j) dist[i][j] = pt[i].dist(pt[j]); for (i=0; i<m; ++i) { scanf("%d", &k); ac.insert(k); } ac.build(); ac.slove(); } return 0; }
Solved 1 / 6 P HDU 4057 Rescue the Rabbit 题意: 给定n个模式串(ATGC),都有一个价值,然后问ATGC组成的长度m的串的最大价值是多少. 注意,每个模式串的价值只能计算一次.可以重叠. (1 ≤ n ≤ 10),l (1 ≤ m ≤ 100) 1 <= wi <= 100 分析: 因为不知道最优是选择几个模式串在答案里, 注意到每个模式串只能计算一次,而且只有十个. 所以,我们可以考虑状压. 我们这样考虑我们的dp状态, bool dp[length][state][state_k],表示这个状态可以到达. 最后我再枚举dp[m][0...qsz][0...state_k],然后根据state_k计算答案. 然后我们发现,内存太大了 会爆...不过length这一维是可以通过滚动数组来优化的,所以就OK. (前面的,有关长度的题也可以用滚动数组优化.)
#include <queue> #include <cstdio> #include <cstring> using namespace std; int n, m; char str[128]; int val[128]; class AC { public: int next[1024][4]; int fail[1024]; int state[1024]; int root, qsz; inline int getid(char ch) { switch (ch) { case 'A': return 0; case 'T': return 1; case 'G': return 2; case 'C': return 3; } } inline int newnode() { int i; for (i=0; i<4; ++i) next[qsz][i] = 0; fail[qsz] = state[qsz] = 0; return qsz++; } void init() { qsz = 0; root = newnode(); } void insert(char s[], int tid) { int i, id, rt = 0; for (i=0; s[i]; ++i) { id = getid(s[i]); if (!next[rt][id]) next[rt][id] = newnode(); rt = next[rt][id]; } state[rt] |= (1 << tid); } inline int getval(int v) { int i, res = 0; for (i=0; i<n; ++i) if (v & (1 << i)) res += val[i]; return res; } void build() { int rt, i; queue<int> q; rt = 0; for (i=0; i<4; ++i) if (next[rt][i]) q.push(next[rt][i]); while(!q.empty()) { rt = q.front(); q.pop(); state[rt] |= state[fail[rt]]; for (i=0; i<4; ++i) { if (!next[rt][i]) next[rt][i] = next[fail[rt]][i]; else { fail[next[rt][i]] = next[fail[rt]][i]; q.push(next[rt][i]); } } } } int dp[2][1024][1024]; int nex; int getans() { int i, j, k, z; memset(dp, 0, sizeof(dp)); int flag = 0; dp[1][0][0] = 1; for (i=0; i<m; ++i,flag^=1) { memset(dp[flag], 0, sizeof(dp[flag])); for (z=0; z<(1<<n); ++z) { for (j=0; j<qsz; ++j) { if (!dp[flag^1][z][j]) continue; for (k=0; k<4; ++k) { nex = next[j][k]; dp[flag][z|state[nex]][nex] = 1; } } } } int res = -862621363; for (i=0; i<(1<<n); ++i) for (j=0; j<qsz; ++j) if (dp[flag^1][i][j]) res = max(res, getval(i)); return res; } }ac; int main() { int i, j, tp; // freopen("E:\\input.txt", "r", stdin); while (~scanf("%d%d", &n, &m)) { ac.init(); for (i=0; i<n; ++i) { scanf("%s%d", str, &tp); val[i] = tp; ac.insert(str, i); } ac.build(); int res = ac.getans(); if (res < 0) printf("No Rabbit after 2012!\n"); else printf("%d\n", res); } return 0; }
Solved 1 / 3 Q HDU 4758 Walk Through Squares 题意: 输入n,m 你可以走n次R m次D. 现在给你两个模式串,问你走出这两个模式串的方案数,可以重叠. 分析: 我们走字符串以后4种状态: 没有走过字符串, 走了一个, 走了另一个, 走了两个. 所以我们有dp数组: dp[state][R][D][4]表示方案数... 然后over.
#include <queue> #include <cstdio> #include <cstring> using namespace std; const int mod = 1000000007; class AC { public: int next[205][2]; int fail[205]; int ed[205]; int qsz, root; // R 1 D 0 inline int getid (char ch) { return ch=='R'; } inline int newnode() { next[qsz][0] = next[qsz][1] = ed[qsz] = fail[qsz] = 0; return qsz++; } init() { qsz = 0; root = newnode(); } void insert(char s[], int tid) { int i, id, rt = 0; for (i=0; s[i]; ++i) { id = getid(s[i]); if (!next[rt][id]) next[rt][id] = newnode(); rt = next[rt][id]; } ed[rt] |= (1 << tid); } void build() { queue<int> q; int i, rt = root; if (next[rt][0]) q.push(next[rt][0]); if (next[rt][1]) q.push(next[rt][1]); while (!q.empty()) { rt = q.front(); q.pop(); ed[rt] |= ed[fail[rt]]; for (i=0; i<2; ++i) { if (!next[rt][i]) next[rt][i] = next[fail[rt]][i]; else { fail[next[rt][i]] = next[fail[rt]][i]; q.push(next[rt][i]); } } } } // R 1 D 0 int dp[201][101][101][4]; int getans(int m, int n) { int R, D, i, j, k, z; memset(dp, 0, sizeof(dp)); dp[0][0][0][0] = 1; for (R=0; R<=n; ++R) for (D=0; D<=m; ++D) for (i=0; i<qsz; ++i) { for (k=0; k<2; ++k) { for (z=0; z<4; ++z) { if (k==0 && D==m) continue; if (k==1 && R==n) continue; int nex = next[i][k]; switch (k) { case 0: dp[nex][R][D+1][z|ed[nex]] = (dp[nex][R][D+1][z|ed[nex]] + dp[i][R][D][z]) % mod; break; case 1: dp[nex][R+1][D][z|ed[nex]] = (dp[nex][R+1][D][z|ed[nex]] + dp[i][R][D][z]) % mod; break; } } } } int res = 0; for (i=0; i<qsz; ++i) res = (res + dp[i][n][m][3]) % mod; return res; } }ac; int main() { int t, n, m, i, j; char str[128]; // freopen("E:\\input.txt", "r", stdin); scanf("%d", &t); while (t--) { ac.init(); scanf("%d%d", &n, &m); scanf("%s", str); ac.insert(str, 0); scanf("%s", str); ac.insert(str, 1); ac.build(); printf("%d\n", ac.getans(m, n)); } return 0; }