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;
}
View Code
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;
}
View Code
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;
}
View Code
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;
}
View Code
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;
}
View Code
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;
}
View Code
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;
}
View Code
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;
}
View Code
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;
}
View Code
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;
}
View Code
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;
}
View Code
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;
}
View Code
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;
}
View Code
然后这是一个类似的题,不过这个题要求只能走一遍.
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;
}
View Code
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;
}
View Code
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;
}
View Code
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;
}
View Code