zoukankan      html  css  js  c++  java
  • 【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
    ", 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
    ", 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
    ", 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
    ", 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)
    构造矩阵即是
    ([egin{matrix} S_n\ A end{matrix}]= [egin{matrix} A & 1\ 0 & 1 end{matrix}] [egin{matrix} S_{n-1}\ A end{matrix}])
    在考虑如何求长度小于L的次数。如果(A_n)来表示长度为n的所有的不包含给定串的trie图,那么同理这个长度小于L的所有trie结果即是(A,A^2,A^3,...,A^L),就将所有矩阵的相应结果相加即可。再仔细看一下,那么可以发现这个式子其实是和上面那个构造矩阵的式子一模一样,只要将trie图所构造出来的矩阵的最后一列全部加上1即可,那么矩阵([egin{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
    ", 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],0leq tleq 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
    ", 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
    ", 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
    ", ++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],0leq 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
    ", ++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
    ", 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
    ", 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<kleq 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
    ", 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;
    }
    
  • 相关阅读:
    简单工厂笔记
    P3369 【模板】普通平衡树 Treap树堆学习笔记
    tp5阿里云短信验证码
    centos 安装php
    tp6.0.2开启多应用模式
    linux navicat最新版过期
    git commit之后 取消commit
    服务器重置之后ssh root@报错
    git pull push 每次都需要输入账号和密码
    跨域问题 php
  • 原文地址:https://www.cnblogs.com/ZX-GO/p/14412964.html
Copyright © 2011-2022 走看看