【kuangbin带你飞】专题十七AC自动机

hdu2222 - Keywords Search

思路
AC自动机的模板题
代码

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
typedef pair<int, int> PII;
const int N = 50 * 10000 + 10, M = 1e6 + 10;
int tr[N][26], ne[N], cnt[N], idx;
char s[M];

void insert(char str[]) {
    int p = 0;
    for(int i = 0; str[i]; i++) {
        int id = str[i] - 'a';
        if(!tr[p][id]) {
            tr[p][id] = ++idx;
            memset(tr[idx], 0, sizeof tr[idx]);
            cnt[idx] = ne[idx] = 0;
        }
        p = tr[p][id];
    }
    cnt[p]++;
}

int q[N];
void build() {
    int l = 1, r = 0;
    for(int i = 0; i < 26; i++) {
        if(tr[0][i]) q[++r] = tr[0][i];
    }
    while(l <= r) {
        int u = q[l++];
        for(int i = 0; i < 26; i++) {
            int c = tr[u][i];
            if(!c) tr[u][i] = tr[ne[u]][i];
            else {
                ne[c] = tr[ne[u]][i];
                q[++r] = c;
            }
        }
    }
}

void solve() {
    int n;
    scanf("%d", &n);
    memset(tr[0], 0, sizeof tr[0]);
    idx = 0, cnt[0] = ne[0] = 0;
    for(int i = 1; i <= n; i++) {
        scanf("%s", s);
        insert(s);
    }
    build();
    scanf("%s", s + 1);
    int len = strlen(s + 1);
    int res = 0;
    for(int i = 1, j = 0; i <= len; i++) {
        int id = s[i] - 'a';
        j = tr[j][id];
        int p = j;
        while(p && ~cnt[p]) {
            res += cnt[p];
            cnt[p] = -1;
            p = ne[p];
        }
    }
    printf("%d\n", res);
}

int main() {
    // freopen("in.txt", "r", stdin);
    int t; cin >> t; while(t--)
    solve();
    return 0;
}

hdu2896 - 病毒侵袭

思路
还是AC自动机模板题,只是在小范围上处理比较复杂,对于每个串单独判断一遍,同时用vector记录一下跑到哪些病毒串即可。
代码

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
typedef pair<int, int> PII;
const int N = 1e5 + 10;
int tr[N][130], ne[N], idx, cnt[N];
bool vis[N];
char str[N];

void insert(char s[], int id) {
    int p = 0;
    for(int i = 0; s[i]; i++) {
        int id = s[i];
        if(!tr[p][id]) {
            tr[p][id] = ++idx;
            memset(tr[idx], 0, sizeof tr[idx]);
            cnt[idx] = 0;
            ne[idx] = 0;
        }
        p = tr[p][id];
    }
    cnt[p] = id;
}

int q[N];
void build() {
    int l = 1, r = 0;
    for(int i = 0; i <= 128; i++) {
        if(tr[0][i]) q[++r] = tr[0][i];
    }
    while(l <= r) {
        int u = q[l++];
        for(int i = 0; i <= 128; i++) {
            int c = tr[u][i];
            if(!c) tr[u][i] = tr[ne[u]][i];
            else {
                ne[c] = tr[ne[u]][i];
                q[++r] = c;
            }
        }
    }
}

void solve() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%s", str);
        insert(str, i);
    }
    build();
    int m; scanf("%d", &m);
    int t = 0;
    for(int id = 1; id <= m; id++) {
        scanf("%s", str + 1);
        vector<int> res;
        memset(vis, 0, sizeof vis);
        for(int i = 1, j = 0; str[i]; i++) {
            int id = str[i];
            j = tr[j][id];
            int p = j;
            while(p && !vis[p]) {
                if(cnt[p]) res.push_back(cnt[p]);
                vis[p] = true;
                p = ne[p];
            }
        }
        if(res.size() > 0) {
            t++;
            sort(res.begin(), res.end());
            printf("web %d:", id);
            for(int i = 0; i < res.size(); i++) {
                printf(" %d", res[i]);
            }
            puts("");
        }
    }
    printf("total: %d\n", t);
}

int main() {
//    freopen("in.txt", "r", stdin);
    solve();
    return 0;
}

hdu3065 - 病毒侵袭持续中

思路
模板题,多开一个cnt数组记录每个病毒串出现几次输出即可。
代码

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
typedef pair<int, int> PII;
const int N = 5e4 + 10, M = 2e6 + 10;
int tr[N][26], ne[N], cnt[N], idx;
char str[M];
char s[1010][55];
int res[1010], n;

void insert(char s[], int id) {
    int p = 0;
    for(int i = 0; s[i]; i++) {
        int id = s[i] - 'A';
        if(!tr[p][id]) {
            tr[p][id] = ++idx;
            memset(tr[idx], 0, sizeof tr[idx]);
            cnt[idx] = ne[idx] = 0;
        }
        p = tr[p][id];
    }
    cnt[p] = id;
}

queue<int> q;
void build() {
    for(int i = 0; i < 26; i++) {
        if(tr[0][i]) q.push(tr[0][i]);
    }
    while(!q.empty()) {
        int u = q.front(); q.pop();
        for(int i = 0; i < 26; i++) {
            int c = tr[u][i];
            if(!c) tr[u][i] = tr[ne[u]][i];
            else {
                ne[c] = tr[ne[u]][i];
                q.push(c);
            }
        }
    }
}

void solve() {
//    int n;
//    scanf("%d", &n);
    idx = 0;
    memset(tr[0], 0, sizeof tr[0]);
    ne[0] = cnt[0] = 0;
    for(int i = 1; i <= n; i++) {
        scanf("%s", s[i]);
        insert(s[i], i);
        res[i] = 0;
    }
    build();
    scanf("%s", str);
    for(int i = 0, j = 0; str[i]; i++) {
        if(str[i] < 'A' || str[i] > 'Z') {
            j = 0;
            continue;
        }
        int id = str[i] - 'A';
        j = tr[j][id];
        int p = j;
        while(p) {
            if(cnt[p]) res[cnt[p]]++;
            p = ne[p];
        }
    }
    for(int i = 1; i <= n; i++) {
        if(res[i]) {
            printf("%s: %d\n", s[i], res[i]);
        }
    }
}

int main() {
//    freopen("in.txt", "r", stdin);
    while(~scanf("%d", &n)) {
        solve();
    }
//    solve();
    return 0;
}

poj2778-DNA Sequence

思路
一开始想着肯定是一个经典的ac自动机+dp题,但是数据范围很大,就需要考虑别的办法。
通过ac自动机建立一个trie图,用vis数组标记一下表示不能到达哪些点。然后通过建好的ac自动机建立矩阵快速幂,矩阵快速幂m[i][j]表示在trie图上从i点到j的方案数。离散数学中可以知道在一个图上走了n次,就相当于是所建矩阵的n次幂。拿矩阵快速幂跑n次以后,计算m[0][i]表示从起点0到tire图上i点的所有方案数,累加一下即可。
代码

#include<iostream>
#include<map>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;

typedef long long LL;
#define int LL
typedef pair<int, int> PII;
#define Martix Matrix
#define gcd __gcd
#define maxn 110
const int mod = 100000;
const int N = 110 + 10;

int tr[N][5], idx, ne[N], n, k;
char s[N];
bool vis[N];
map<char, int> mp;

struct Matrix {
    int m[N][N];
    Matrix() {
        memset(m, 0, sizeof(m));    // 初始化矩阵 
    }
};

Matrix Multi(Matrix a, Matrix b)    // 矩阵乘法 
{
    Matrix res;
    for(int i = 0; i <= idx; i++) {
        for(int j = 0; j <= idx; j++) {
            for(int k = 0; k <= idx; k++) {
                res.m[i][j] = (res.m[i][j] + a.m[i][k] * b.m[k][j]) % mod;
            }
        }
    }
    return res;
}

Martix fastm(Martix a, int n)       // 矩阵快速幂 
{
    Martix res;
    for(int i = 0; i <= idx; i++) {    // 等同于快速幂的res = 1的操作 
        res.m[i][i] = 1;
    }
    while(n) {
        if(n & 1) res = Multi(res, a);
        a = Multi(a, a);
        n >>= 1;
    }
    return res;
}

void insert(char str[]) {
    int p = 0;
    for(int i = 0; str[i]; i++) {
        int id = mp[str[i]];
        if(!tr[p][id]) {
            tr[p][id] = ++idx;
            memset(tr[idx], 0, sizeof tr[idx]);
            vis[idx] = false;
            ne[idx] = 0;
        }
        p = tr[p][id];
    }
    vis[p] = true;
}

void build() {
    queue<int> q;
    for(int i = 0; i < 4; i++) {
        if(tr[0][i]) q.push(tr[0][i]);
    }
    while(!q.empty()) {
        int u = q.front(); q.pop();
        for(int i = 0; i < 4; i++) {
            int c = tr[u][i];
            if(!c) tr[u][i] = tr[ne[u]][i];
            else {
                ne[c] = tr[ne[u]][i];
                vis[c] |= vis[ne[c]];
                q.push(c);
            }
        }
    }
}

void solve() {
    memset(tr[0], 0, sizeof tr[0]);
    vis[0] = false, ne[0] = 0; idx = 0;
    for(int i = 1; i <= n; i++) {
        scanf("%s", s);
        insert(s);
    }
    build();

    Matrix x;
    for(int i = 0; i <= idx; i++) {
        if(vis[i]) continue;
        for(int j = 0; j < 4; j++) {
            if(!vis[tr[i][j]]) {
                x.m[i][tr[i][j]]++;
            }
        }
    }
    x = fastm(x, k);
    LL res = 0;
    for(int i = 0; i <= idx; i++) {
        res = (res + x.m[0][i]) % mod;
    }
    printf("%lld\n", res);
}

void init() {
    mp['A'] = 0, mp['G'] = 1, mp['C'] = 2, mp['T'] = 3;
}

signed main() {
    init();
    // freopen("in.txt", "r", stdin);
    // int t; cin >> t; while(t--)
    while(~scanf("%lld%lld", &n, &k))
    solve();
    return 0;
}

hdu2243-考研路茫茫――单词情结

思路
题目中要求所给单词至少出现一次,那么反向考虑就是所有的可能性减去不会出现的次数即可。那么就和上一题poj2778一样,稍微不同的就是不止是要算长度为L的不可能次数,也要计算小于L的所有不可能次数。
先考虑长度为L时的所有方案数,假设\(S_n\)表示长度为n的所有方案数,那么即可得
\(S_{n-1}=A+A^2+A^3+...+A^{n-1}\)
\(S_n=A+A^2+A^3+...+A^n=A(1+A+A^2+...+A^{n-1})=A*S_{n-1}+A\)
构造矩阵即是
\([\begin{matrix} S_n\\ A \end{matrix}]= [\begin{matrix} A & 1\\ 0 & 1 \end{matrix}] [\begin{matrix} S_{n-1}\\ A \end{matrix}]\)
在考虑如何求长度小于L的次数。如果\(A_n\)来表示长度为n的所有的不包含给定串的trie图,那么同理这个长度小于L的所有trie结果即是\(A,A^2,A^3,...,A^L\),就将所有矩阵的相应结果相加即可。再仔细看一下,那么可以发现这个式子其实是和上面那个构造矩阵的式子一模一样,只要将trie图所构造出来的矩阵的最后一列全部加上1即可,那么矩阵\([\begin{matrix} A & 1\\ 0 & 1 \end{matrix}]^n\)以后,\(m[0][1]\)位置所表示的即是\(A+A^2+...+A^{L-1}\)的结果,\(m[0][0]\)即是\(A^L\)的结果。那么全部加一下即可,具体也可以看一下代码中的内容。
代码

#include<iostream>
#include<map>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;

typedef unsigned long long LL;
#define int LL
typedef pair<int, int> PII;
#define Martix Matrix
#define gcd __gcd
const int mod = 100000;
const int N = 30 + 10;

int n, k;
int tr[N][26], ne[N], idx;
bool vis[N];
char s[N];

struct Matrix {
    int m[N][N];
    Matrix() {
        memset(m, 0, sizeof(m));    // 初始化矩阵 
    }
};

Matrix Multi(Matrix a, Matrix b)    // 矩阵乘法 
{
    Matrix res;
    for(int i = 0; i <= idx; i++) {
        for(int j = 0; j <= idx; j++) {
            for(int k = 0; k <= idx; k++) {
                res.m[i][j] += a.m[i][k] * b.m[k][j];
            }
        }
    }
    return res;
}

Martix fastm(Martix a, int n)       // 矩阵快速幂 
{
    Martix res;
    for(int i = 0; i <= idx; i++) {    // 等同于快速幂的res = 1的操作 
        res.m[i][i] = 1;
    }
    while(n) {
        if(n & 1) res = Multi(res, a);
        a = Multi(a, a);
        n >>= 1;
    }
    return res;
}

void insert(char str[]) {
    int p = 0;
    for(int i = 0; str[i]; i++) {
        int id = str[i] - 'a';
        if(!tr[p][id]) {
            tr[p][id] = ++idx;
            memset(tr[idx], 0, sizeof tr[idx]);
            vis[idx] = ne[idx] = 0;
        }
        p = tr[p][id];
    }
    vis[p] = true;
}

void build() {
    queue<int> q;
    for(int i = 0; i < 26; i++) {
        if(tr[0][i]) q.push(tr[0][i]);
    }
    while(!q.empty()) {
        int u = q.front(); q.pop();
        for(int i = 0; i < 26; i++) {
            int c = tr[u][i];
            if(!c) tr[u][i] = tr[ne[u]][i];
            else {
                ne[c] = tr[ne[u]][i];
                vis[c] |= vis[ne[c]];
                q.push(c);
            }
        }
    }
}

void solve() {
    memset(tr[0], 0, sizeof tr[0]);
    vis[0] = false, ne[0] = 0; idx = 0;
    for(int i = 1; i <= n; i++) {
        scanf("%s", s);
        insert(s);
    }
    build();

    Matrix x;
    for(int i = 0; i <= idx; i++) {
        if(vis[i]) continue;
        for(int j = 0; j < 26; j++) {
            if(!vis[tr[i][j]]) {
                x.m[i][tr[i][j]]++;
            }
        }
    }

    idx++;
    for(int i = 0; i <= idx; i++) {
        x.m[i][idx] = 1;
    }

    x = fastm(x, k);

    LL res = 0;
    for(int i = 0; i <= idx; i++) {
        res = res + x.m[0][i];
    }
    res -= 1;

    Matrix a, b;
    a.m[0][0] = 26;
    a.m[0][1] = a.m[1][1] = 1;
    a.m[1][0] = 0;
    a = fastm(a, k);
    LL res2 = a.m[0][0] + a.m[0][1] - 1;

    printf("%llu\n", res2 - res);
}

signed main() {
    // freopen("in.txt", "r", stdin);
    // int t; cin >> t; while(t--)
    while(~scanf("%lld%lld", &n, &k))
    solve();
    return 0;
}

hdu2825- Wireless Password

思路
AC自动机+状压DP
先建立AC自动机,同时对于所给的一些字符串结尾用二进制记录一下对应的状态。
\(dp[i][j][k]\):对于密码串在第i位,自动机上位置为j结点,状态为k的所有方案数。
转移方程:\(dp[i+1][p][k|vis[p]]+=dp[i][j][k],p=tr[j][t],0\leq t\leq 25\),这个t就是每个小写字母对应的ascii码。就是考虑当前第i位在自动机上对应的结点为j,状态为k,那么考虑下一位的情况,t即使枚举下一位所有可能的字符,对应自动机下一个跳到的结点就是\(p=tr[j][t]\),状态为\(k|vis[p]\)
代码

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
typedef pair<int, int> PII;
#define gcd __gcd
const int mod = 20090717;
const int N = 100 + 10;

int n, m, k;
char s[N];
int tr[N][26], ne[N], idx;
int vis[N], dp[30][N][1100];
int mp[1100];

void insert(char s[], int id) {
    int p = 0;
    for(int i = 0; s[i]; i++) {
        int id = s[i] - 'a';
        if(!tr[p][id]) {
            tr[p][id] = ++idx;
            memset(tr[idx], 0, sizeof tr[idx]);
            ne[idx] = vis[idx] = 0;
        }
        p = tr[p][id];
    }
    vis[p] |= (1 << id);
}

void build() {
    queue<int> q;
    for(int i = 0; i < 26; i++) {
        if(tr[0][i]) q.push(tr[0][i]);
    }
    while(!q.empty()) {
        int u = q.front(); q.pop();
        for(int i = 0; i < 26; i++) {
            int c = tr[u][i];
            if(!c) tr[u][i] = tr[ne[u]][i];
            else {
                ne[c] = tr[ne[u]][i];
                vis[c] |= vis[ne[c]];
                q.push(c);
            }
        }
    }
}

void solve() {
    memset(tr[0], 0, sizeof tr[0]);
    ne[0] = vis[0] = 0;
    idx = 0;
    memset(dp, 0, sizeof dp);

    for(int i = 0; i < m; i++) {
        scanf("%s", s);
        insert(s, i);
    }
    build();

    dp[0][0][0] = 1;
    for(int i = 0; i < n; i++) {
        for(int j = 0; j <= idx; j++) {
            for(int l = 0; l < (1 << m); l++) {
                if(!dp[i][j][l]) continue;
                for(int r = 0; r < 26; r++) {
                    int p = tr[j][r];
                    int t = l | vis[p];
                    dp[i + 1][p][t] += dp[i][j][l] % mod;
                    dp[i + 1][p][t] %= mod;
                }
            }
        }
    }
    int res = 0;
    for(int i = 0; i < (1 << m); i++) {
        if(mp[i] >= k) {
            for(int j = 0; j <= idx; j++) {
                res += dp[n][j][i] % mod;
                res %= mod;
            }
        }
    }

    printf("%d\n", res);
}

void init() {
    for(int i = 0; i < (1 << 10); i++) {
        mp[i] = 0;
        for(int j = 0; j < 10; j++) {
            if((i >> j) & 1) mp[i]++;
        }
    }
}

int main() {
    init();
    // freopen("in.txt", "r", stdin);
    while(~scanf("%d%d%d", &n, &m, &k)) {
        if(n == 0 && m == 0 && k == 0) break;
        solve();
    }
    return 0;
}

hdu2296-Ring

思路
这题如果光只要输出最后的代价值为多少,那么就是一道简单的ac自动机dp问题,可是要考虑输出最优方案,最好的解决办法就是在dp的过程中同时记录每一位相对应的字符串即可。
\(dp[i][j]\):字符串在第i位,自动机上结点位j时的最大价值。
\(str[i][j]\):字符串在第i位,自动机上结点位j时的最大价值时,所对应的字符串。
转移方程:\(dp[i+1][p]=max(dp[i][j]+val[p]),p=tr[j][t]\),这里的p,t的意义和上面一题一样。在更新答案的过程中,实时更新这个str[i][j],最后遍历一遍结果得到最小字符串即可。具体看代码。
代码

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
typedef pair<int, int> PII;
#define gcd __gcd
const int mod = 20090717;
const int N = 1100 + 10;

char s[110][12];
int tr[N][26], ne[N], val[N], idx, dp[55][N];
char str[55][N];
char res[55], tmp[55], pre[55][N][55];

void insert(char s[], int d) {
    int p = 0;
    for(int i = 0; s[i]; i++) {
        int id = s[i] - 'a';
        if(!tr[p][id]) {
            tr[p][id] = ++idx;
            memset(tr[idx], 0, sizeof tr[idx]);
            ne[idx] = val[idx] = 0;
        }
        p = tr[p][id];
    }
    val[p] += d;
}

void build() {
    queue<int> q;
    for(int i = 0; i < 26; i++) {
        if(tr[0][i]) q.push(tr[0][i]);
    }
    while(!q.empty()) {
        int u = q.front(); q.pop();
        for(int i = 0; i < 26; i++) {
            int c = tr[u][i];
            if(!c) tr[u][i] = tr[ne[u]][i];
            else {
                ne[c] = tr[ne[u]][i];
                val[c] += val[ne[u]];
                q.push(c);
            }
        }
    }
}

bool cmp(char s1[], char s2[]) {
    int len1 = strlen(s1);
    int len2 = strlen(s2);
    if(len1 != len2) return len1 < len2;
    return strcmp(s1, s2) < 0;
}

void solve() {
    memset(tr[0], 0, sizeof tr[0]);
    idx = 0;
    ne[0] = val[0] = 0;

    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= m; i++) {
        scanf("%s", s[i]);
    }
    for(int i = 1; i <= m; i++) {
        int x;
        scanf("%d", &x);
        insert(s[i], x);
    }
    build();

    memset(dp, -1, sizeof dp);
    memset(pre, 0, sizeof pre);
    memset(res, 0, sizeof res);
    dp[0][0] = 0;
    int mx = 0;
    for(int i = 0; i < n; i++) {
        for(int j = 0; j <= idx; j++) {
            if(dp[i][j] == -1) continue;
            strcpy(tmp, pre[i][j]);
            int len = strlen(tmp);

            for(int k = 0; k < 26; k++) {
                int p = tr[j][k];
                tmp[len] = k + 'a';
                tmp[len + 1] = 0;
                // cout << tmp << endl;
                if(dp[i][j] + val[p] > dp[i + 1][p]) {
                    dp[i + 1][p] = dp[i][j] + val[p];
                    strcpy(pre[i + 1][p], tmp);
                }
                else if(dp[i][j] + val[p] == dp[i + 1][p] && cmp(tmp, pre[i + 1][p])) {
                    strcpy(pre[i + 1][p], tmp);
                }
            }
        }
    }

    for(int i = 1; i <= n; i++) {
        for(int j = 0; j <= idx; j++) {
            if(mx < dp[i][j]) {
                mx = dp[i][j];
                strcpy(res, pre[i][j]);
            }
            else if(mx == dp[i][j] && cmp(pre[i][j], res)) {
                strcpy(res, pre[i][j]);
            }
        }
    }
    if(mx == 0) puts("");
    else printf("%s\n", res);
    // cout << res << endl;
}

int main() {
    // init();
    // freopen("in.txt", "r", stdin);
    int t; cin >> t; while(t--)
    solve();
    return 0;
}

hdu2457- DNA repair

思路
依然是那么个套路。
\(dp[i][j]\):到字符串第i位,自动机上第j位时的最小代价。
因为这个是考虑到修改的问题,按照常规先建立自动机,只有在dp的时候考虑一下这个结点是否合法,在合法的情况下考虑所给的字符串是否要修改,如果要修改那么代价就+1.
转移方程:\(dp[i+1][p]=min(dp[i][p],dp[i][j]+(s[i+1]==k)),p=tr[j][t]\),这里的p,t和hdu2825那题题解意义一样。k表示枚举的字符,即k为"AGCT"其中一个字符,判断是否和下一位相同。具体转移方程详见代码。
代码

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
typedef pair<int, int> PII;
#define gcd __gcd
const int mod = 100000;
const int N = 1000 + 10;

int n, kase;
int tr[N][4], idx, ne[N], dp[N][N];
bool vis[N];
char s[N];
map<char, int> mp;

void insert(char s[]) {
    int p = 0;
    for(int i = 0; s[i]; i++) {
        int id = mp[s[i]];
        if(!tr[p][id]) {
            tr[p][id] = ++idx;
            memset(tr[idx], 0, sizeof tr[idx]);
            vis[idx] = 0;
            ne[idx] = 0;
        }
        p = tr[p][id];
    }
    vis[p] = true;
}

void build() {
    queue<int> q;
    for(int i = 0; i < 4; i++) {
        if(tr[0][i]) q.push(tr[0][i]);
    }
    while(!q.empty()) {
        int u = q.front(); q.pop();
        for(int i = 0; i < 4; i++) {
            int c = tr[u][i];
            if(!c) tr[u][i] = tr[ne[u]][i];
            else {
                ne[c] = tr[ne[u]][i];
                vis[c] |= vis[ne[c]];
                q.push(c);
            }
        }
    }
}

void solve() {
    memset(tr[0], 0, sizeof tr[0]);
    idx = 0;
    vis[0] = ne[0] = 0;
    for(int i = 1; i <= n; i++) {
        scanf("%s", s);
        insert(s);
    }
    build();
    scanf("%s", s + 1);
    memset(dp, 0x3f, sizeof dp);
    for(int i = 0; i <= idx; i++) dp[0][i] = 0;
    int len = strlen(s + 1);
    for(int i = 0; i < len; i++) {
        for(int j = 0; j <= idx; j++) {
            for(int k = 0; k < 4; k++) {
                int val = mp[s[i + 1]] != k;
                int p = tr[j][k];
                if(!vis[p]) dp[i + 1][p] = min(dp[i + 1][p], dp[i][j] + val);
            }
        }
    }
    int res = 1e9;
    for(int i = 0; i <= idx; i++) {
        res = min(res, dp[len][i]);
    }
    printf("Case %d: %d\n", ++kase, res == 1e9 ? -1 : res);
}

signed main() {
    mp['A'] = 0, mp['G'] = 1, mp['C'] = 2, mp['T'] = 3;
    // freopen("in.txt", "r", stdin);
    // int t; cin >> t; while(t--)
    while(~scanf("%d", &n)) {
        if(n == 0) break;
        solve();
    }
    // solve();
    return 0;
}

hdu3341-Lost's revenge

思路
和前几种问题稍稍有点不同,但大体思路框架还是一模一样的。
因为要考虑字符串的重排,那么就需要统计"AGCT"每个字符的个数,再拿dp开就变成了\(dp[i][j][][][][]\),那么空间一定爆炸,考虑到字符串的长度也是一定的,那么后面的四维可以压到一维,用字符串hash表示。相当于用字符串的进制表示。假如有cnt[0]个A,cnt[1]个G,cnt[2]个C,cnt[3]个T,那么考虑用进制表示每一个串,每出现一个A相当于在\((cnt[1]+1)*(cnt[2]+1)*(cnt[3]+1)\)上增加一位,同理G相当于\((cnt[2]+1)*(cnt[3]+1)\)上表示。
那么dp就可以考虑每次增加的是哪一个字母,同时记录一下最新的状态。
\(dp[i][j]\):在自动机上为i点时,状态为j的所有方案数。
对应的转移方程即为
\(dp[p][state]=dp[j][id]+cnt[p],p=tr[j][t],0\leq t<4,state=id+t*st[t]\),就是相当于枚举每一位增加的字母,然后id为当前状态,state为加上当前字母t之后的下一个状态,j为当前自动机上的结点,p为下一个结点。
代码

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
typedef pair<int, int> PII;
#define gcd __gcd
const int mod = 1000000007;
const int N = 500 + 10, M = 18;

int tr[N][4], idx, ne[N], dp[N][11 * 11 * 11 * 11], cnt[N];
char s[N];
map<char, int> mp;
int a[4], st[4], kase, n;

void insert(char s[]) {
    int p = 0;
    for(int i = 0; s[i]; i++) {
        int id = mp[s[i]];
        if(!tr[p][id]) {
            tr[p][id] = ++idx;
            memset(tr[idx], 0, sizeof tr[idx]);
            ne[idx] = cnt[idx] = 0;
        }
        p = tr[p][id];
    }
    cnt[p]++;
}

void build() {
    queue<int> q;
    for(int i = 0; i < 4; i++) {
        if(tr[0][i]) q.push(tr[0][i]);
    }
    while(!q.empty()) {
        int u = q.front(); q.pop();
        for(int i = 0; i < 4; i++) {
            int c = tr[u][i];
            if(!c) tr[u][i] = tr[ne[u]][i];
            else {
                ne[c] = tr[ne[u]][i];
                cnt[c] += cnt[ne[c]];
                q.push(c);
            }
        }
    }
}

void solve() {
    memset(tr[0], 0, sizeof tr[0]);
    ne[0] = cnt[0] = 0;
    idx = 0;
    memset(a, 0, sizeof a);
    memset(st, 0, sizeof st);

    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%s", s);
        insert(s);
    }
    build();
    memset(dp, -1, sizeof dp);
    scanf("%s", s + 1);
    int len = strlen(s + 1);
    for(int i = 1; i <= len; i++) {
        a[mp[s[i]]]++;
    }
    st[0] = (a[1] + 1) * (a[2] + 1) * (a[3] + 1);
    st[1] = (a[2] + 1) * (a[3] + 1);
    st[2] = a[3] + 1;
    st[3] = 1;
    dp[0][0] = 0;
    int res = 0;
    for(int A = 0; A <= a[0]; A++) {
        for(int G = 0; G <= a[1]; G++) {
            for(int C = 0; C <= a[2]; C++) {
                for(int T = 0; T <= a[3]; T++) {
                    int id = A * st[0] + G * st[1] + C * st[2] + T * st[3];
                    for(int j = 0; j <= idx; j++) {
                        if(dp[j][id] == -1) continue;
                        for(int k = 0; k < 4; k++) {
                            if(A == a[0] && k == 0) continue;
                            if(G == a[1] && k == 1) continue;
                            if(C == a[2] && k == 2) continue;
                            if(T == a[3] && k == 3) continue;
                            int p = tr[j][k];
                            int state = id + st[k];
                            dp[p][state] = max(dp[p][state], dp[j][id] + cnt[p]);
                        }
                    }
                }
            }
        }
    }
    int t = a[0] * st[0] + a[1] * st[1] + a[2] * st[2] + a[3] * st[3];
    for(int i = 0; i <= idx; i++) {
        res = max(res, dp[i][t]);
    }

    printf("Case %d: %d\n", ++kase, res);
}

int main() {
    // freopen("in.txt", "r", stdin);
    mp['A'] = 0, mp['G'] = 1, mp['C'] = 2, mp['T'] = 3;
    // int t; cin >> t; while(t--)
    // solve();

    while(~scanf("%d", &n)) {
        if(!n) break;
        solve();
    }
    return 0;
}

hdu3247 - Resource Archiver

思路
n的数据范围只有10,一开始想的是设置状态\(dp[i][j]\)表示第一维当前选择好字符串的状态为i,那么第一维最对为(1<<10),此时AC自动机上的状态为j。转移方程即为\(dp[i|vis[p]][p]=min(dp[i][j]+1)\),\(vis[i]\)表示AC自动机上状态为i时所对应选择的字符串状态。但是这么考虑空间范围到了1e3*6e4,感觉会炸,无从下手学习了一波题解。
考虑到最后贡献转移出来的状态一定是可以被选择的字符串状态,有很多病毒串是无效的,就只要考虑那些能构成的字符串。有效状态只有10个串,可以预处理dis[i][j]表示第i个字符串和第j个字符串合法拼接的最短长度,通过ac自动机的一些性质可以很快求出来他们的最长后缀,也可以求出这个最短长度。最后\(dp[i][j]\)就变成了状态为i,AC自动机上合法状态为j的最短长度。转移方程具体看代码吧。
代码

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
typedef pair<int, int> PII;
#define gcd __gcd
const int inf = 0x3f3f3f3f;
const int mod = 20090717;
const int N = 6e4 + 10;

int tr[N][2], ne[N], st[N], idx, dp[1100][15], n, m, cnt, ok[15];
bool vis[N];
char s[N];

void insert(char s[], int d) {
   int p = 0;
   for(int i = 0; s[i]; i++) {
       int id = s[i] - '0';
       if(!tr[p][id]) {
           tr[p][id] = ++idx;
           memset(tr[idx], 0, sizeof tr[idx]);
           ne[idx] = st[idx] = vis[idx] = 0;
       }
       p = tr[p][id];
   }
   if(d >= 0) st[p] |= (1 << d);
   else vis[p] = true;
}

void build() {
   queue<int> q;
   for(int i = 0; i < 2; i++) {
       if(tr[0][i]) q.push(tr[0][i]);
   }

   while(!q.empty()) {
       int u = q.front(); q.pop();
       for(int i = 0; i < 2; i++) {
           int c = tr[u][i];
           if(!c) tr[u][i] = tr[ne[u]][i];
           else {
               ne[c] = tr[ne[u]][i];
               vis[c] |= vis[ne[c]];
               q.push(c);
           }
       }
   }
}

int d[N], dis[15][15];
void bfs(int s) {
   memset(d, -1, sizeof d);
   queue<int> q;
   d[ok[s]] = 0;
   q.push(ok[s]);
   while(!q.empty()) {
       int u = q.front(); q.pop();
       for(int i = 0; i < 2; i++) {
           int v = tr[u][i];
           if(d[v] < 0 && !vis[v]) {
               d[v] = d[u] + 1;
               q.push(v);
           }
       }
   }
   for(int i = 0; i < cnt; i++) {
       dis[s][i] = d[ok[i]];
   }
}

void init() {
   memset(tr[0], 0, sizeof tr[0]);
   ne[0] = vis[0] = st[0] = idx = 0;
   cnt = 1;
   memset(dis, 0x3f, sizeof dis);
   memset(ok, 0, sizeof ok);
}

void solve() {
   init();
   for(int i = 0; i < n; i++) {
       scanf("%s", s);
       insert(s, i);
   }
   for(int i = 1; i <= m; i++) {
       scanf("%s", s);
       insert(s, -1);
   }
   build();
   for(int i = 1; i <= idx; i++) {
       if(st[i]) ok[cnt++] = i;
   }
   for(int i = 0; i < cnt; i++) {
       bfs(i);
   }
   memset(dp, -1, sizeof dp);
   dp[0][0] = 0;
   for(int i = 0; i < (1 << n); i++) {
       for(int j = 0; j < cnt; j++) {
           if(dp[i][j] == -1) continue;
           for(int k = 0; k < cnt; k++) {
               if(dis[j][k] < 0) continue;
               int nxt = i | st[ok[k]];
               if(dp[nxt][k] == -1) {
                   dp[nxt][k] = dp[i][j] + dis[j][k];
               }
               else {
                   dp[nxt][k] = min(dp[nxt][k], dp[i][j] + dis[j][k]);
               }
           }
       }
   }

   int res = 1e9;
   for(int i = 0; i < cnt; i++) {
       if(dp[(1 << n) - 1][i] != -1) res = min(res, dp[(1 << n) - 1][i]);
   }
   printf("%d\n", res);
}

int main() {
   // init();
   // freopen("in.txt", "r", stdin);
   // int t; cin >> t; while(t--)
   // solve();

   while(~scanf("%d%d", &n, &m)) {
       if(!n && !m) break;
       solve();
   }
   return 0;
}

hdu4758- Walk Through Squares

思路
一个二维的自动机+状压dp,就相对于最基础的自动机改良了一下。
\(dp[i][j][k][st]\):走到位置(i, j)时,在自动机上k结点,状态为st的最终方案数。
因为有"R"和"D"的限制,就考虑要往下走还是往右走,枚举的时候考虑一下即可。
状态转移方程
\(dp[i+1][j][p][cnt[p]|t]+=dp[i][j][k][t]\),此时枚举的那个字母应该为"D"
\(dp[i][j+1][p][cnt[p]|t]+=dp[i][j][k][t]\),此时枚举的那个字母应该为"R"
具体结合代码看应该更容易理解
代码

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
typedef pair<int, int> PII;
#define gcd __gcd
const int inf = 0x3f3f3f3f;
const int mod = 1000000007;
const int N = 200 + 10;

int tr[N][2], ne[N], cnt[N], idx, dp[110][110][N][4];
char s[N];
map<char, int> mp;

void insert(char s[], int t) {
   int p = 0;
   for(int i = 0; s[i]; i++) {
       int id = mp[s[i]];
       if(!tr[p][id]) {
           tr[p][id] = ++idx;
           memset(tr[idx], 0, sizeof tr[idx]);
           ne[idx] = cnt[idx] = 0;
       }
       p = tr[p][id];
   }
   cnt[p] |= (1 << t);
}

void build() {
   queue<int> q;
   for(int i = 0; i < 2; i++) {
       if(tr[0][i]) q.push(tr[0][i]);
   }
   while(!q.empty()) {
       int u = q.front(); q.pop();
       for(int i = 0; i < 2; i++) {
           int c = tr[u][i];
           if(!c) tr[u][i] = tr[ne[u]][i];
           else {
               ne[c] = tr[ne[u]][i];
               cnt[c] |= cnt[ne[c]];
               q.push(c);
           }
       }
   }
}

void solve() {
   memset(tr[0], 0, sizeof tr[0]);
   ne[0] = cnt[0] = 0;
   idx = 0;
   int m, n;
   scanf("%d%d", &m, &n);
   for(int i = 0; i < 2; i++) {
       scanf("%s", s);
       insert(s, i);
   }
   build();
   memset(dp, 0, sizeof dp);
   dp[0][0][0][0] = 1;
   for(int i = 0; i <= n; i++) {
       for(int j = 0; j <= m; j++) {
           for(int k = 0; k <= idx; k++) {
               for(int t = 0; t < 4; t++) {
                   if(dp[i][j][k][t] == 0) continue;
                   int p0 = tr[k][0], p1 = tr[k][1];
                   dp[i][j + 1][p0][cnt[p0] | t] = (1LL * dp[i][j][k][t] + 1LL * dp[i][j + 1][p0][cnt[p0] | t]) % mod;
                   // dp[i][j + 1][p0][cnt[p0] | t] %= mod;
                   dp[i + 1][j][p1][cnt[p1] | t] = (1LL * dp[i][j][k][t] + 1LL * dp[i + 1][j][p1][cnt[p1] | t]) % mod;
                   // dp[i + 1][j][p1][cnt[p1] | t] %= mod;
               }
           }
       }
   }
   LL res = 0;
   for(int i = 0; i <= idx; i++) {
       res = (1LL * dp[n][m][i][3] + 1LL * res) % mod;
       // res %= mod;
   }
   printf("%lld\n", res);
}

int main() {
   mp['R'] = 0, mp['D'] = 1;
   // freopen("in.txt", "r", stdin);
   int t; cin >> t; while(t--)
   solve();
   return 0;
}

hdu4511-小明系列故事――女友的考验

思路
表面上是个图论题,其实是个ac自动机。如果在比赛中碰到我应该也想不到用ac自动机来搞。
通过建立AC自动机形成的trie图来表示哪些方式是不能到达终点的,然后建立dp。
\(dp[i][j]\):走到结点i,在自动机上结点为j的最小距离
\(dp[k][p]=min(dp[i][j]+dis(i,k)),p=tr[j][k],i<k\leq n\)表示从i走到k结点的最小代价,注意一些限制条件即可。
代码

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
typedef pair<int, int> PII;
#define gcd __gcd
const int inf = 0x3f3f3f3f;
const int mod = 1000000007;
const int N = 500 + 10;

int tr[N][55], ne[N], idx;
bool vis[N];
double x[N], y[N], dp[55][N];
int n, m;
vector<int> s;

void insert() {
    int len = s.size(), p = 0;
    for(int i = 0; i < len; i++) {
        int id = s[i];
        if(!tr[p][id]) {
            tr[p][id] = ++idx;
            memset(tr[idx], 0, sizeof tr[idx]);
            ne[idx] = vis[idx] = 0;
        }
        p = tr[p][id];
    }
    vis[p] = true;
}

void build() {
    queue<int> q;
    for(int i = 1; i <= n; i++) {
        if(tr[0][i]) q.push(tr[0][i]);
    }
    while(!q.empty()) {
        int u = q.front(); q.pop();
        for(int i = 1; i <= n; i++) {
            int c = tr[u][i];
            if(!c) tr[u][i] = tr[ne[u]][i];
            else {
                ne[c] = tr[ne[u]][i];
                vis[c] |= vis[ne[c]];
                q.push(c);
            }
        }
    }
}

double dis(int a, int b) {
    return sqrt((x[a] - x[b]) * (x[a] - x[b]) + (y[a] - y[b]) * (y[a] - y[b]));
}

void solve() {
    memset(tr[0], 0, sizeof tr[0]);
    idx = 0;
    ne[0] = vis[0] = 0;

    for(int i = 1; i <= n; i++) {
        scanf("%lf%lf", &x[i], &y[i]);
    }
    for(int i = 1; i <= m; i++) {
        int k;
        scanf("%d", &k);
        s.clear();
        for(int i = 1; i <= k; i++) {
            int x; scanf("%d", &x);
            s.push_back(x);
        }
        insert();
    }
    build();
    for(int i = 0; i <= n; i++) {
        for(int j = 0; j <= idx; j++) {
            dp[i][j] = 1e18;
        }
    }
    dp[1][tr[0][1]] = 0;
    for(int i = 1; i < n; i++) {
        for(int j = 0; j <= idx; j++) {
            if(dp[i][j] == inf) continue;
            for(int k = i + 1; k <= n; k++) {
                int p = tr[j][k];
                if(!vis[p]) dp[k][p] = min(dp[k][p], dp[i][j] + dis(i, k));
            }
        }
    }
    double res = 1e18;
    for(int i = 0; i <= idx; i++) {
        res = min(res, dp[n][i]);
    }
    if(res == 1e18) puts("Can not be reached!");
    else printf("%.2f\n", res);
}

int main() {
    // freopen("in.txt", "r", stdin);
    // int t; cin >> t; while(t--)
    // solve();

    while(~scanf("%d%d", &n, &m)) {
        if(!n && !m) break;
        solve();
    }
    return 0;
}
posted @ 2021-02-18 18:04  这知识他不进我的脑子  阅读(91)  评论(0编辑  收藏  举报