• AC自动机


    AC 自动机

    1. Dominating Patterns

    UVA - 1449

    给N个串,然后再给一个串s,求N个串总共在S中出现了多少次

    将N个串插入到AC自动机当中,如果某个结点为模式串末尾结点,则在fail树中,以该节点为祖先的所有结点,贡献都+1

    然后直接暴力匹配即可,最后倒着去算一遍贡献。(具体体现在fail树中,如果一个结点匹配上了,那么他的祖先一定都可以匹配上,按序号降序去累加可以保证顺序是正确的,因为一个结点的fail指针所指结点的序号一定更小)

    需要注意的是,N个串中可能有相同的。

    const int N = 1000010 + 5;
    int n;
    char s[N], P[200][100];
    map<string,int> mp;
    namespace AC{
        int tr[N][26],tot;
        int e[N],fail[N];
        int cnt[N];
        queue<int> q;
        void init(){
            for(int i=0;i<=tot;i++){
                memset(tr[i],0,sizeof tr[i]);
                fail[i] = e[i] = cnt[i] = 0;
            }
            tot = 0;
        }
        void insert(char *s){
            int u = 0;
            int len = strlen(s + 1);
            for(int i=1;i<=len;i++){
                if(!tr[u][s[i] - 'a']){
                    tr[u][s[i] - 'a'] = ++tot;
                }
                u = tr[u][s[i] - 'a'];
            }
            mp[string(s+1)] = u;
            e[u] ++;
        }
        void build(){
            for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
            while(q.size()){
                int u = q.front();q.pop();
                for(int i=0;i<26;i++){
                    if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                    else tr[u][i] = tr[fail[u]][i];
                }
            }
        }
        void get(char *t){
            int u = 0,res = 0;
            for(int i=1;t[i];i++){
                u = tr[u][t[i] - 'a'];
                cnt[u] ++;
            }
            for(int i=tot;i>=0;i--) cnt[fail[i]] += cnt[i];
            for(int i=0;i<=tot;i++) if(e[i]){
                res = max(res, cnt[i]);
            }
            printf("%d
    ", res);
            for(int i=1;i<=n;i++){
                if(cnt[mp[string(P[i]+1)]] == res) printf("%s
    ", P[i]+1);
            }
        }
    }
    
    
    int main() {
        while(~scanf("%d",&n)){
            if(n == 0) break;
            mp.clear();
            AC::init();
            for(int i=1;i<=n;i++){
                scanf("%s", P[i]+1);
                AC::insert(P[i]);
            }
            AC::build();
            scanf("%s",s+1);
            AC::get(s);
        }
        return 0;
    }
    

    2. Substring

    UVA - 11468

    给 K 个串,再给一个大小为N的字符集,给出从N个字符中选第 i 个的概率 p_i, 然后每次从中选出一个字符,选L次拼成一个长为L的字符串,求K个串不出现在该串中的概率。

    AC自动机中的trans函数,即状态转移函数,如果 (trans[u][c]) 所指向的结点是一个字符串的末尾结点或者是末尾结点在fail树上的子孙,那么该转移不能选择,如果选择则意味着有一个字符串会出现在构造的这个串中。

    到这里就变成了一个概率DP的问题,为了降低编程难度,可以直接用记忆化搜索实现。

    还需要注意的是字符集的问题,它包括了大小写以及数字。

    const int N = 1010;
    int n, k, L, vis[1010][200], in[300];
    double p[300], d[1010][200];
    char s[22][22];
    namespace AC{
        int tr[N][300],tot;
        int e[N],fail[N];
        int cnt[N];
        queue<int> q;
        void init(){
            for(int i=0;i<=tot;i++){
                memset(tr[i],0,sizeof tr[i]);
                fail[i] = e[i] = cnt[i] = 0;
            }
            tot = 0;
        }
        void insert(char *s){
            int u = 0;
            int len = strlen(s + 1);
            for(int i=1;i<=len;i++){
                if(!tr[u][s[i]]){
                    tr[u][s[i]] = ++tot;
                }
                u = tr[u][s[i]];
            }
            e[u] ++;
        }
        void build(){
            for(int i=0;i<130;i++)if(tr[0][i])q.push(tr[0][i]);
            while(q.size()){
                int u = q.front();q.pop();
                e[u] |= e[fail[u]]; // e[u]=1则表示u结点所表示的状态的某一个后缀对应K个字符串中的某个
                for(int i=0;i<130;i++){
                    if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                    else tr[u][i] = tr[fail[u]][i];
                }
            }
        }
        double get(int u, int L){
            if(!L) return 1.0;
            if(vis[u][L]) return d[u][L];
            vis[u][L] = 1;
            double& ans = d[u][L];
            ans = 0;
            for(int i=0;i<130;i++){
                //in[i] 则表示该字符出现在题目给定的字符集中
                if(in[i] && !e[tr[u][i]]) ans += p[i] * get(tr[u][i], L-1);
            }
            return ans;
        }
    }
    int main() {
        int T;scanf("%d",&T);
        int cas = 0;
        while(T--){
            memset(vis, 0, sizeof vis);
            memset(in, 0, sizeof in);
            AC::init();
            scanf("%d",&k);
            for(int i=1;i<=k;i++){
                scanf("%s", s[i]+1);
                AC::insert(s[i]);
            }
            scanf("%d",&n);
            for(int i=1;i<=n;i++){
                char op[10];scanf("%s", op);
                scanf("%lf", &p[op[0]]);
                in[op[0]] = 1;
            }
            AC::build();
            scanf("%d",&L);
            printf("Case #%d: %.6f
    ", ++cas, AC::get(0, L));
        }
        return 0;
    }
    

    3. Matrix Matcher

    UVA - 11019

    题意:给出一个$nm$的字符矩阵$T$,你的任务是找出给定的$xy$ 的字符矩阵$P$ 出现了多少次。

    分析:将 (P) 的每一行加入到AC自动机中,然后对于$T$ 的每一行,去自动机中暴力匹配,如果找到了 (T) 的第 (row) 行的第 (i) 个位置匹配到了 (P) 第 $j$行字符的最后一个位置,说明在 (T[row][i-y+1]) 开始的位置可以匹配 P 的 (j) 行。这样,以 (T[row-j+1][i-y+1]) 为左上角的,大小为$x*y$ 的子矩阵中,新增加了一行可以与 (P) 匹配,这个信息用$match[i][j]$ 来存放,每次遇到使其加一。

    最后跑一遍,满足$match[i][j] = x$ 的那些$(i,j)$ 就是一个二位匹配点。

    const int N = 100000 + 5;
    int n, m, x, y;
    char s[1010][1010], t[110][110];
    int match[1010][1010];
    namespace AC{
        int tr[N][26],tot;
        int e[N],fail[N];
        int cnt[N];
        vector<int> ends[N]; 
        queue<int> q;
        void init(){
            memset(match, 0, sizeof match);
            for(int i=0;i<=tot;i++){
                memset(tr[i],0,sizeof tr[i]);
                fail[i] = e[i] = cnt[i] = 0;
                ends[i].clear();
            }
            tot = 0;
        }
        void insert(char *s, int pos){
            int u = 0;
            int len = strlen(s + 1);
            for(int i=1;i<=len;i++){
                if(!tr[u][s[i] - 'a']){
                    tr[u][s[i] - 'a'] = ++tot;
                }
                u = tr[u][s[i] - 'a'];
            }
            e[u] ++;
            ends[u].push_back(pos); // 将行序号加到对应结点末尾
        }
        void build(){
            for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
            while(q.size()){
                int u = q.front();q.pop();
                for(int i=0;i<26;i++){
                    if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                    else tr[u][i] = tr[fail[u]][i];
                }
            }
        }
        void solve(){
            for(int row=1;row<=n;row++){
                int u = 0;
                for(int i = 1; i <= m; i++){
                    u = tr[u][s[row][i] - 'a'];
                    //这里并不需要跳fail,因为这些模式串的长度都一定
                    for(auto pos : ends[u]){
                        match[row - pos + 1][i - y + 1] ++;
                    }
                }
            }
            int res = 0;
            for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(match[i][j] == x) res++;
            printf("%d
    ", res);
        }
    }
    
    int main() {
        int T;scanf("%d",&T);
        while(T--){
            AC::init();
            scanf("%d%d",&n,&m);
            for(int i=1;i<=n;i++){
                scanf("%s",s[i]+1);
            }
            scanf("%d%d",&x,&y);
            for(int i=1;i<=x;i++){
                scanf("%s",t[i]+1);
                AC::insert(t[i], i);
            }
            AC::build();
            AC::solve();
        }
        return 0;
    }
    

    4. Detect the Virus

    ZOJ - 3430

    比较恶心的多模式串匹配,需要预处理输入,将base64转换为uchar类型

    const int inf = 0x3f3f3f3f;
    const int N = 100000 + 5;
    char cb64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    int id[200], a[100000];
    char s[10000];
    int t[10000];
    int n;
    void init(){
        for(int i=0;i<64;i++){
            id[cb64[i]] = i;
        }
    }
    void pushback(int *a, int &pos, int x){
        for(int i=0;i<6;i++){
            a[pos++] = x >> (5-i) & 1;
        }
    }
    int convert(char * s, int *t){ // s为base64,t为二进制
        int len = strlen(s), pos = 0, top = 0;
        for(int i=0;i<len;i++){
            if(s[i] == '=')continue;
            pushback(a,pos, id[s[i]]);
        }
        int x = 0;
        for(int i=0;i<pos;i++){
            x = x * 2 + a[i];
            if((i+1) % 8 == 0){
                t[top++] = x;
                x = 0;
            }
        }
        return top;
    }
    namespace AC{
        int tr[N][256],tot;
        int e[N],fail[N];
        int st[N], top, v[N];
        queue<int> q;
        void init(){
            for(int i=0;i<=tot;i++){
                memset(tr[i],0,sizeof tr[i]);
                fail[i] = e[i] = 0;
            }
            tot = 0;
        }
        void insert(int *s, int len){
            int u = 0;
            for(int i=0;i<len;i++){
                if(!tr[u][s[i]]){
                    tr[u][s[i]] = ++tot;
                }
                u = tr[u][s[i]];
            }
            e[u] ++;
        }
        void build(){
            for(int i=0;i<256;i++)if(tr[0][i])q.push(tr[0][i]);
            while(q.size()){
                int u = q.front();q.pop();
                for(int i=0;i<256;i++){
                    if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                    else tr[u][i] = tr[fail[u]][i];
                }
            }
        }
        void solve(int *s, int len){
            int res = 0, u = 0;
            for(int i=0;i<len;i++){
                u = tr[u][s[i]];
                for(int j=u; j && !v[j]; j=fail[j]){
                    res += e[j];
                    st[++top] = j;
                    v[j] = 1;
                }
            }
            for(int i=1;i<=top;i++)v[st[i]] = 0;
            top = 0;
            printf("%d
    ", res);
        }
    }
    int main(){
        init();
        while(~scanf("%d",&n)){
            AC::init();
            for(int i=1;i<=n;i++){
                scanf("%s",s);
                int len = convert(s, t);
                AC::insert(t, len);
            }
            AC::build();
            scanf("%d",&n);
            while(n--){
                scanf("%s",s);
                int len = convert(s, t);
                AC::solve(t, len);
            }
            puts("");
        }
        return 0;
    }
    

    5. [P2414 NOI2011]阿狸的打字机

    不难想到每个字符串结尾结点在fail树上的子孙代表的含义,对于一个询问$(x,y)$ ,要求的就是 x 号字符串的末尾结点的子树中,有多少个结点可以匹配到 y 中。

    暴力的把所以标记打到fail树上?N个字符串,每个字符串长度不固定,有可能都很长,显然不行。每次对单个询问处理貌似都不太合适,可以把询问离线,集中处理询问。

    先将fail树构建dfs序列,那么询问某个结点的子树情况可以转换为一个序列问题,接下来可以dfs扫描原Trie树(不是AC自动机构建出来的字典图),每次扫描到一个结点时,若从根节点到该结点的路径表示为询问中的 y,那么扫描它的所有询问 x,在fail树dfs序上找答案即可。

    #define mk make_pair
    const int N = 100000 + 5;
    char s[N];
    int n, m;
    vector<pair<int,int>> query[N];
    int c[N], res[N], pos[N];
    void add(int x, int y){
        for(;x<N;x+=x&-x) c[x]+=y;
    }
    int ask(int x){
        int res = 0;
        for(;x;x-=x&-x) res+=c[x];
        return res;
    }
    namespace AC{
        int tr[N][26],tot, trie[N][26];
        int e[N], fail[N], fa[N];
        int u;
        int dfn[N], sz[N], cnt;
        vector<int> ends[N];
        vector<int> v[N];
        queue<int> q;
        void init(){
            for(int i=0;i<=tot;i++){
                memset(tr[i],0,sizeof tr[i]);
                fail[i] = e[i] = 0;
            }
            tot = 0;
            u = 0;
        }
        void insert(char c){
            if(!tr[u][c-'a'])tr[u][c-'a'] = ++tot,fa[tot] = u;
            u = tr[u][c-'a'];
        }
        void setend(int id){
            e[u] ++;
            ends[u].push_back(id);
            pos[id] = u;
        }
        void getup(){
            if(u != 0)
                u = fa[u];
        }
        void insert(char *s){
            int u = 0;
            int len = strlen(s + 1);
            for(int i=1;i<=len;i++){
                if(!tr[u][s[i] - 'a']){
                    tr[u][s[i] - 'a'] = ++tot;
                }
                u = tr[u][s[i] - 'a'];
            }
            e[u] ++;
        }
        void build(){
            for(int i=0;i<=tot;i++) memcpy(trie[i], tr[i], sizeof tr[i]);//保留原Trie树
            for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
            while(q.size()){
                int u = q.front();q.pop();
                if(u) v[fail[u]].push_back(u);//构建fail树
                for(int i=0;i<26;i++){
                    if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                    else tr[u][i] = tr[fail[u]][i];
                }
            }
        }
        void dfs(int u){//构建fail树dfs序列
            dfn[u] = ++cnt;
            sz[u] = 1;
            for(auto x : v[u]){
                dfs(x);
                sz[u] += sz[x];
            }
        }
        void solve(int u){
            add(dfn[u], 1);
            for(auto y : ends[u]){
                for(auto t : query[y]){
                    int x = t.first;
                    int id = t.second;
                    int dfnid = dfn[pos[x]];
                    res[id] = ask(dfnid + sz[pos[x]] - 1) - ask(dfnid-1);
                }
            }
            for(int i=0;i<26;i++){
                if(trie[u][i])solve(trie[u][i]);
            }
            add(dfn[u], -1);
        }
    }
    
    
    int main() {
        scanf("%s",s);
        AC::init();
        int len = strlen(s);
        n = 0;
        for(int i=0;i<len;i++){
            if(s[i] == 'B')AC::getup();
            else if(s[i] == 'P'){
                AC::setend(++n);
            }else AC::insert(s[i]);
        }
        AC::build();
        AC::dfs(0);
        scanf("%d",&m);
        for(int i=1;i<=m;i++){
            int x,y;scanf("%d%d",&x,&y);
            query[y].push_back(mk(x, i));//离线询问
        }
        AC::solve(0);
        for(int i=1;i<=m;i++) printf("%d
    ", res[i]);
        return 0;
    }
    

    6. DNA repair

    HDU - 2457

    AC自动机上面DP即可,注意要记忆化以下

    const int N = 1000 + 5;
    int n;
    char s[N];
    int xid[26];
    namespace AC{
        int tr[N][4],tot;
        int e[N],fail[N];
        int vis[N][N], d[N][N];
        queue<int> q;
        void init(){
            for(int i=0;i<=tot;i++){
                memset(vis[i], 0, sizeof vis[i]);
                memset(tr[i],0,sizeof tr[i]);
                fail[i] = e[i] = 0;
            }
            tot = 0;
        }
        void insert(char *s){
            int u = 0;
            int len = strlen(s + 1);
            for(int i=1;i<=len;i++){
                int c = xid[s[i] - 'A'];
                if(!tr[u][c]){
                    tr[u][c] = ++tot;
                }
                u = tr[u][c];
            }
            e[u] ++;
        }
        void build(){
            for(int i=0;i<4;i++)if(tr[0][i])q.push(tr[0][i]);
            while(q.size()){
                int u = q.front();q.pop();
                e[u] |= e[fail[u]];
                for(int i=0;i<4;i++){
                    if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                    else tr[u][i] = tr[fail[u]][i];
                }
            }
        }
        int get(int u, int pos, int len){
            if(e[u]) return inf;//这个要放在最前面,u肯定是合法结点(不会超过tot),由于我第一次写好没放最前面,debug好久
            if(pos == len + 1) return 0;
            if(vis[u][pos]) return d[u][pos];
            vis[u][pos] = 1;
            int &res = d[u][pos];
            res = inf;
            int c = xid[s[pos] - 'A'];
            for(int i=0;i<4;i++){
                res = min(res, (i != c) + get(tr[u][i], pos+1, len));
            }
            return res;
        }
    }
    
    
    int main() {
        xid['A' - 'A'] = 0;
        xid['G' - 'A'] = 1;
        xid['C' - 'A'] = 2;
        xid['T' - 'A'] = 3;
        int cas = 0;
        while(scanf("%d",&n) != EOF){
            if(n == 0) break;
            AC::init();
            for(int i=1;i<=n;i++){
                scanf("%s",s+1);
                AC::insert(s);
            }
            AC::build();
            scanf("%s",s+1);
            int res = AC::get(0, 1, strlen(s+1));
            if(res == inf) res = -1;
            printf("Case %d: %d
    ",++cas, res);
        }
        return 0;
    }
    

    7. Ring

    HDU - 2296

    同样也是AC自动机上面DP,其实很好做的题,肯定是由于出题人懒得写spj导致一堆特殊条件

    (d[u][i]) 表示从 (u) 结点出发,走 (i) 的长度,最多可以匹配多少价值,(p[u][i]) 表示路径。

    先求出$d[0][n]$, 然后找一个最小的 i , 使得出$d[0][i] == d[0][n]$,然后按照路径输出。

    由于枚举时是按照字典序从小到大的,所以这里直接输出即可

    const int N = 2000 + 5;
    const int M = 55;
    char s[N];
    int n, m, pos[N];
    namespace AC{
        int tr[N][26],tot;
        int e[N],fail[N];
        int vis[N][M], d[N][M], p[N][M];
        queue<int> q;
        void init(){
            for(int i=0;i<=tot;i++){
                memset(vis[i], 0, sizeof vis[i]);
                memset(tr[i],0,sizeof tr[i]);
                memset(p, 0, sizeof p);
                fail[i] = e[i] = 0;
            }
            tot = 0;
        }
        void insert(char *s, int id){
            int u = 0;
            int len = strlen(s + 1);
            for(int i=1;i<=len;i++){
                int c = s[i] - 'a';
                if(!tr[u][c]){
                    tr[u][c] = ++tot;
                }
                u = tr[u][c];
            }
            pos[id] = u;
        }
        void build(){
            for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
            while(q.size()){
                int u = q.front();q.pop();
                e[u] += e[fail[u]];//权值累加
                for(int i=0;i<26;i++){
                    if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                    else tr[u][i] = tr[fail[u]][i];
                }
            }
        }
        int get(int u, int n){ // 从 u 开始 n 长度
            if(n == 0) return 0;
            if(vis[u][n]) return d[u][n]; // 记忆化搜索
            vis[u][n] = 1;
            int &ans = d[u][n];
            ans = 0;
            for(int i=0;i<26;i++){
                int c = tr[u][i];
                int now = e[c] + get(c, n - 1);
                if(now > ans){ // 必须为大于号,因为答案要求字典序最小
                    ans = now;
                    p[u][n] = i;
                }
            }
            return ans;
        }
        void print(int u, int n){
            if(n == 0) return;
            int c = p[u][n];
            printf("%c", char('a'+c));
            print(tr[u][c], n-1);
        }
    }
    
    int main() {
        int T;
        scanf("%d",&T);
        while(T--){
            AC::init();
            scanf("%d%d",&n,&m);
            for(int i=1;i<=m;i++){
                scanf("%s",s+1);
                AC::insert(s, i);
            }
            for(int i=1;i<=m;i++){
                scanf("%d", &AC::e[pos[i]]);
            }
            AC::build();
            AC::get(0, n);
            for(int i=0;i<=n;i++){
                if(AC::get(0, i) == AC::get(0, n)){ //这里一定要用这种方式访问dp值!!!,由于是记忆化搜索所以再调用函数之前不一定会有d[0][i],被这里坑了
                    n = i;
                    break;
                }
            }
            AC::print(0,n);
            puts("");
        }
        return 0;
    }
    

    8. Searching the String

    ZOJ - 3228

    暴力跳$fail$进行统计。每次跳到一个结点$ j$,表示该结点表示的子串在 (s) 中出现了一次,对于第一类询问,直接++,对于第二类询问,需要利用上一次该位置在 (s) 中出现的位置$last$,如果 (i - last >= len(j)),则表示两次出现在 (s) 中不交叉,则答案++.

    因为本身模式串的长度最长才6,暴力跳fail几乎对时间没有影响。但如果还想优化,可以这样考虑,每次暴力跳fail的结果不一定会对计算结果有用,因为有一些结点并不是末尾结点,为了防止跳到这些没有用的结点,再用一个$pre$ 数组来记录,在$bfs$ 的过程中可以这么计算:

    if(e[fail[u]]) pre[u] = fail[u];

    else pre[u] = pre[fail[u]];

    实际优化不是很明显:

    image.png

    const int N = 600000 + 5;
    const int M = 100010;
    int n;
    char s[M], t[10];
    int tp[M], res[N][2], last[N], dep[N], pos[M];
    namespace AC{
        int tr[N][26],tot;
        int e[N],fail[N], pre[N];
        queue<int> q;
        void init(){
            for(int i=0;i<=tot;i++){
                memset(tr[i],0,sizeof tr[i]);
                res[i][0] = res[i][1] = 0;
                fail[i] = e[i] = 0;
                last[i] = 0;
            }
            tot = 0;
        }
        void insert(char *s, int id){
            int u = 0;
            int len = strlen(s + 1);
            for(int i=1;i<=len;i++){
                if(!tr[u][s[i] - 'a']){
                    tr[u][s[i] - 'a'] = ++tot;
                }
                u = tr[u][s[i] - 'a'];
            }
            e[u] ++;
            dep[u] = len;
            pos[id] = u;
        }
        void build(){
            for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
            while(q.size()){
                int u = q.front();q.pop();
                if(e[fail[u]]) pre[u] = fail[u];
                else pre[u] = pre[fail[u]];
                for(int i=0;i<26;i++){
                    if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                    else tr[u][i] = tr[fail[u]][i];
                }
            }
        }
        void get(){
            int len = strlen(s+1);
            int u = 0;
            for(int i=1;i<=len;i++){
                int c = s[i] - 'a';
                u = tr[u][c];
                for(int j = u; j; j = pre[j]){
                    if(e[j]){
                        res[j][0] ++;
                        if(i - last[j] >= dep[j]){
                            last[j] = i;
                            res[j][1] ++;
                        }
                    }
                }
            }
        }
    }
    int main() {
        int cas = 0;
        while(~scanf("%s",s+1)){
            AC::init();
            scanf("%d",&n);
            for(int i=1;i<=n;i++){
                scanf("%d%s",&tp[i], t+1);
                AC::insert(t, i);
            }
            AC::build();
            AC::get();
            printf("Case %d
    ", ++cas);
            for(int i=1;i<=n;i++){
                printf("%d
    ", res[pos[i]][tp[i]]);
            }
            puts("");
        }
        return 0;
    }
    

    9. Frequency of String

    http://codeforces.com/problemset/problem/963/D

    给出一个字符串S, n次询问,每次询问给出正整数k和字符串m, 要求字符串T的最小长度,满足T是S的子串,且m在T中出现了至少K次。

    首先想一下暴力做法:将所有询问串构建AC自动机,然后在上面跑S,若在$s[i]$ 匹配到了一个串,那么就给这个串添加一个 i 位置为结尾的出现。最后进行统计即可(连续 k 次出现的最小长度)。

    每次匹配到一个位置几乎都要暴力跳 fail,因为这个题目中多模式串的长度有长有短,很容易出现某个串是另外一个串的后缀这种情况,所以很容易T。但根据上一道题目中提到的小优化进行优化呢?每次只给对答案有用的结点添加出现位置,那最终添加的次数就是每个询问串在 s 中的所有出现位置。似乎还是会有很多。

    注意到,题目中说明了每个询问串都不一样。对于长度一样的询问串,在S中最多有$|S|$ 次出现,而对于总长度为$sum|m|$ 的多模式串来讲,(|m|) 最多有$sqrt{sum|m|}$ 种,也就是将$sum|m|$ 最多分成$sqrt{sum|m|}(个不同的数字)(sum = frac{n*(n+1)}{2})$ (n) 差不多在$sqrt$ 级别。

    所以最后最多会添加$sqrt{sum|m|} imes|S|$ 次位置。

    const int N = 100000 + 5;
    int n;
    char s[N], t[N];
    vector<int> pos[N];
    int p[N], k[N], len[N];
    namespace AC{
        int tr[N][26],tot;
        int e[N], fail[N], pre[N];
        queue<int> q;
        void insert(char *s, int id){
            int u = 0;
            int len = strlen(s + 1);
            for(int i=1;i<=len;i++){
                if(!tr[u][s[i] - 'a']){
                    tr[u][s[i] - 'a'] = ++tot;
                }
                u = tr[u][s[i] - 'a'];
            }
            e[u] ++;
            p[u] = id;
        }
        void build(){
            for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
            while(q.size()){
                int u = q.front();q.pop();
                if(e[fail[u]]) pre[u] = fail[u];
                else pre[u] = pre[fail[u]];
                for(int i=0;i<26;i++){
                    if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                    else tr[u][i] = tr[fail[u]][i];
                }
            }
        }
        void get(){
            int len = strlen(s+1);
            int u = 0;
            for(int i=1;i<=len;i++){
                int c = s[i] - 'a';
                u = tr[u][c];
                for(int j = u; j; j = pre[j]){
                    if(e[j]){
                        pos[p[j]].push_back(i);
                    }
                }
            }
        }
    }
    int main() {
        scanf("%s",s+1);
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf("%d%s",&k[i], t+1);
            len[i] = strlen(t+1);
            AC::insert(t, i);
        }
        AC::build();
        AC::get();
        for(int i=1;i<=n;i++){
            if(pos[i].size() < k[i]){
                puts("-1");continue;
            }
            int res = inf;
            for(int j = k[i] - 1; j < pos[i].size(); j++){
                res = min(res, pos[i][j] - pos[i][j - k[i] + 1] + len[i]);
            }
            printf("%d
    ", res);
        }
        return 0;
    }
    

    10. Resource Archiver

    HDU - 3247

    很疑惑为什么网上有那么多错误题解?

    下面这份代码建立在资源串不存在某个是某个子串的情况,再建好的Trie图中,从每个资源串末尾进行BFS,求出它到其他资源串末尾结点的最短距离(不能经过病毒串末尾结点),当然还要处理空串到其他所有资源串的距离。

    然后跑一个TSP就可以了

    const int N = 100000 + 5;
    int n, m;
    char s[N];
    namespace AC{
        int tr[N][2],tot;
        int e[N],fail[N], vir[N], pos[20];
        int dis[20][20], d[N], dp[1<<10][20];//dp[i][j]表示资源串状态为 i(二进制),走到了 j 号资源串的末尾结点
        queue<int> q;
        void init(){
            memset(dis, -1, sizeof dis);
            for(int i=0;i<=tot;i++){
                memset(tr[i],0,sizeof tr[i]);
                fail[i] = e[i] = vir[i] = 0;
            }
            tot = 0;
        }
        void insert(char *s, int id, int type){
            int u = 0;
            int len = strlen(s + 1);
            for(int i=1;i<=len;i++){
                if(!tr[u][s[i] - '0']){
                    tr[u][s[i] - '0'] = ++tot;
                }
                u = tr[u][s[i] - '0'];
            }
            if(type == -1){
                vir[u] = 1;//标记病毒
            }else{
                pos[id] = u;    
            }
        }
        void build(){
            for(int i=0;i<2;i++)if(tr[0][i])q.push(tr[0][i]);
            while(q.size()){
                int u = q.front();q.pop();
                vir[u] |= vir[fail[u]];//fail树上进行传递
                for(int i=0;i<2;i++){
                    if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                    else tr[u][i] = tr[fail[u]][i];
                }
            }
        }
        void bfs(int s){
            queue<int> q;
            memset(d, -1, sizeof d);//-1表示不能访问,这里要和dis的默认一致,都为-1
            d[pos[s]] = 0; // 时刻注意pos[s]表示Trie图中s号资源串的结点编号
            q.push(pos[s]);
            
            while(q.size()){
                int x = q.front();q.pop();
                for(int i=0;i<2;i++){
                    int u = tr[x][i];
                    if(!vir[u] && d[u] == -1){
                        d[u] = d[x] + 1;
                        q.push(u);
                    }
                }
            }
            for(int i=1;i<=n;i++){
                dis[s][i] = d[pos[i]];
            }
        }
        void solve(){
            memset(dp, 0x3f, sizeof dp);
            dp[0][0] = 0;
            for(int i=0; i<(1<<n); i++){
                for(int j=0; j<=n; j++){ //j要从0开始,
                    for(int k = 1; k <= n; k++){ //k从1开始就行,0代表空串
                        if(dis[j][k] == -1)continue;
                        if(i >> (k-1) & 1) continue;
                        int &res = dp[i|(1<<(k-1))][k];
                        res = min(res, dp[i][j] + dis[j][k]);
                    }
                }
            }
            int res = inf;
            for(int i=1;i<=n;i++)
                res = min(res, dp[(1<<n)-1][i]);
            printf("%d
    ", res);
        }
    }
    int main() {
        while(~scanf("%d%d",&n,&m)){
            if(n == 0 && m == 0){
                break;
            }
            AC::init();
            for(int i=1;i<=n;i++){
                scanf("%s",s+1);
                AC::insert(s, i, i);
            }
            for(int i=1;i<=m;i++){
                scanf("%s",s+1);
                AC::insert(s, -1, -1);
            }
            AC::build();
            AC::bfs(0);
            for(int i=1;i<=n;i++) AC::bfs(i);
            AC::solve();
        }
        return 0;
    }
    

    11. 小明系列故事——女友的考验

    HDU - 4511

    自动机上面DP,和前面的题目基本一样

    const int N = 500 + 5;
    const int M = 55;
    int n, m, k;
    int a[N];
    double x[M], y[M];
    double d[55][55];
    namespace AC{
        int tr[N][55],tot;
        int e[N],fail[N];
        int vis[N][55];
        double res[N][55];
        queue<int> q;
        void init(){
            for(int i=0;i<=tot;i++){
                memset(tr[i],0,sizeof tr[i]);
                memset(vis, 0, sizeof vis);
                fail[i] = e[i] = 0;
            }
            tot = 0;
        }
        void insert(int *s, int len){
            int u = 0;
            for(int i=1;i<=len;i++){
                if(!tr[u][s[i]]){
                    tr[u][s[i]] = ++tot;
                }
                u = tr[u][s[i]];
            }
            e[u] ++;
        }
        void build(){
            for(int i=1;i<=n;i++)if(tr[0][i])q.push(tr[0][i]);
            while(q.size()){
                int u = q.front();q.pop();
                e[u] |= e[fail[u]];
                for(int i=1;i<=n;i++){
                    if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                    else tr[u][i] = tr[fail[u]][i];
                }
            }
        }
        double get(int u, int pos){
            if(e[u]) return 1e15;
            if(pos == n) return 0;
            if(vis[u][pos]) return res[u][pos];
            vis[u][pos] = 1;
            double &ans = res[u][pos];
            ans = 1e15;
            for(int i=pos+1;i<=n;i++){
                ans = min(ans, d[pos][i] + get(tr[u][i], i));
            }
            return ans;
        }
    }
    double S(double x){ return x * x;}
    int main() {
        while(~scanf("%d%d",&n,&m)){
            if(n == 0 && m == 0) break;
            AC::init();
            for(int i=1;i<=n;i++){
                scanf("%lf%lf",&x[i], &y[i]);//一开始就要输入double,因为下面计算距离时,有可能爆LL
            }
            for(int i=1;i<=n;i++){
                for(int j=1;j<=n;j++){
                    d[i][j] = sqrt(S(x[i]-x[j]) + S(y[i]-y[j]));
                }
            }
            for(int i=1;i<=m;i++){
                scanf("%d",&k);
                for(int j=1;j<=k;j++)scanf("%d",&a[j]);
                AC::insert(a, k);
            }
            AC::build();
            double res =  AC::get(AC::tr[0][1], 1);
            if(res == 1e15)puts("Can not be reached!");
            else 
                printf("%.2f
    ", res);
        }
        return 0;
    }
    

    12. Wireless Password

    HDU - 2825

    题意:

    给出$m$个长度小于等于$10$的字符串,然后求长度为$n(nle 25)$的字符串,要求其最少包含$m$个字符串中的$k$个(可以有覆盖部分),问这样的字符串有多少个。

    分析:

    d[i][j][k] 表示长度为 i,在 j 结点,包含字符串状态为 k 的种类数

    考虑 AC自动机上 j 结点的转移,u = tr[j][c], e[u]表示u结点的匹配集合,则有:

    d[i+1][u][k|e[u]] += d[i][j][k]

    const int N = 100 + 5;
    const int mod = 20090717;
    int n, m, k;
    char s[N];
    int d[26][N][1<<10], cnt[1<<10];
    namespace AC{
        int tr[N][26],tot;
        int e[N],fail[N];
        queue<int> q;
        void init(){
            for(int i=0;i<=tot;i++){
                memset(tr[i],0,sizeof tr[i]);
                fail[i] = e[i] = 0;
            }
            tot = 0;
        }
        void insert(char *s, int id){
            int u = 0;
            int len = strlen(s + 1);
            for(int i=1;i<=len;i++){
                if(!tr[u][s[i] - 'a']){
                    tr[u][s[i] - 'a'] = ++tot;
                }
                u = tr[u][s[i] - 'a'];
            }
            e[u] |= 1 << (id-1);
        }
        void build(){
            for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
            while(q.size()){
                int u = q.front();q.pop();
                e[u] |= e[fail[u]];
                for(int i=0;i<26;i++){
                    if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                    else tr[u][i] = tr[fail[u]][i];
                }
            }
        }
        void solve(){
            memset(d, 0, sizeof d);
            d[0][0][0] = 1;
            //25 * 100 * 1024 * 26
            for(int i=0;i<n;i++){
                for(int j=0;j<=tot;j++){
                    for(int k=0;k<(1<<m);k++){
                        //很强大的优化
                        if(d[i][j][k] == 0) continue;
                        for(int c = 0; c < 26; c++){
                            int u = tr[j][c];
                            int &res = d[i+1][u][k|e[u]];
                            res = (res + d[i][j][k]) % mod;
                        }
                    }
                }
            }
            int res = 0;
            for(int i=0;i<(1<<m);i++){
                if(cnt[i] < k) continue;
                for(int j=0;j<=tot;j++){
                    res = (res + d[n][j][i]) % mod;
                }
            }
            printf("%d
    ", res);
        }
    }
    
    int main() {
        for(int i=0;i<(1<<10);i++){
            for(int j=0;j<10;j++){
                if(i >> j & 1) cnt[i]++;
            }
        }
        while(~scanf("%d%d%d",&n,&m,&k)){
            if(n == 0 && m == 0 && k == 0) break;
            AC::init();
            for(int i=1;i<=m;i++){
                scanf("%s",s+1);
                AC::insert(s, i);
            }
            AC::build();
            AC::solve();
        }
        return 0;
    }
    

    13.Lost's revenge

    HDU - 3341

    题意:

    DNA序列,仅包含"ACGT"四种字符,给 (N(Nle 50)) 个串,每个串长度不超过10,最后给出一个字符串S$(|S| le 40)$, 求将 S 重新排列之后,最多能够包含N个串中的多少个?(允许覆盖)

    分析:

    重组串长度不超过40,字符种类数为4,如果用d[i][j][k][l][m]来表示匹配到 i 号结点时,有 j 个 'A', k 个 'C', l 个 'G', m 个 'T'时,最多包含几个串

    枚举 i 的转移,u=tr[i][c], 若 c 表示 'A', 则 d[u][j+1][k][l][m] = max(d[u][j+1][k][l][m], d[i][j][k][l][m] + e[u])

    const int N = 500 + 5;
    int n;
    char s[N];
    int f[4];
    inline int xid(char ch) {
        if(ch == 'A') return 0;
        if(ch == 'C') return 1;
        if(ch == 'G') return 2;
        if(ch == 'T') return 3;
    }
    namespace AC{
        int tr[N][4],tot;
        int e[N],fail[N];
        queue<int> q;
        vector<vector<vector<vector<int>>>> d[N];
        void init(){
            for(int i=0;i<=tot;i++){
                memset(tr[i],0,sizeof tr[i]);
                fail[i] = e[i] = 0;
            }
            tot = 0;
        }
        void insert(char *s){
            int u = 0;
            int len = strlen(s + 1);
            for(int i=1;i<=len;i++){
                int c = xid(s[i]);
                if(!tr[u][c]){
                    tr[u][c] = ++tot;
                }
                u = tr[u][c];
            }
            e[u]++;
        }
        void build(){
            for(int i=0;i<4;i++)if(tr[0][i])q.push(tr[0][i]);
            while(q.size()){
                int u = q.front();q.pop();
                e[u] += e[fail[u]];
                for(int i=0;i<4;i++){
                    if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                    else tr[u][i] = tr[fail[u]][i];
                }
            }
        }
        void solve(){
            memset(f, 0, sizeof f);
            int len = strlen(s+1);
            for(int i=1;i<=len;i++){
                f[xid(s[i])] ++;
            }
    
            for(int i = 0; i <= tot; i++){
                d[i].clear();
                d[i].resize(f[0]+1);
                for(int j = 0; j <= f[0]; j++){
                    d[i][j].resize(f[1] + 1);
                    for(int k = 0; k <= f[1]; k++){
                        d[i][j][k].resize(f[2]+1);
                        for(int l = 0; l <= f[2]; l++){
                            d[i][j][k][l].resize(f[3]+1, -1);
                        }
                    }
                }
            }
            d[0][0][0][0][0] = 0;
            int res = 0;
            for(int num = 0; num <= len; num ++){
                for(int i = 0; i <= tot; i++){
                    for(int j = 0; j <= f[0] && j <= num; j++){
                        for(int k = 0; k <= f[1] && j + k <= num; k++){
                            for(int l = 0; l <= f[2] && j + k + l <= num; l++){
                                int m = num - j - k - l;
                                if(m > f[3] || d[i][j][k][l][m] == -1) continue;
    
                                if(j < f[0]) d[tr[i][0]][j+1][k][l][m] = max(d[tr[i][0]][j+1][k][l][m], d[i][j][k][l][m] + e[tr[i][0]]);
                                if(k < f[1]) d[tr[i][1]][j][k+1][l][m] = max(d[tr[i][1]][j][k+1][l][m], d[i][j][k][l][m] + e[tr[i][1]]);
                                if(l < f[2]) d[tr[i][2]][j][k][l+1][m] = max(d[tr[i][2]][j][k][l+1][m], d[i][j][k][l][m] + e[tr[i][2]]);
                                if(m < f[3]) d[tr[i][3]][j][k][l][m+1] = max(d[tr[i][3]][j][k][l][m+1], d[i][j][k][l][m] + e[tr[i][3]]);
                                
                                if(num == len);
                                res = max(res, d[i][j][k][l][m]);
                                
                            }
                        }
                    }
                }
            }
            printf("%d
    ", res);
        }
    }
    
    
    int main() {
        int cas = 0;
        while(~scanf("%d",&n)){
            if(n == 0) break;
            AC::init();
            for(int i=1;i<=n;i++){
                scanf("%s",s+1);
                AC::insert(s);
            }
            AC::build();
            scanf("%s", s+1);
            printf("Case %d: ", ++cas);
            AC::solve();
        }
        return 0;
    }
    

    14 DNA Sequence

    POJ - 2778

    参考题解:https://blog.csdn.net/morgan_xww/article/details/7834801

    const int N = 100 + 5;
    const int mod = 100000;
    int n, m;
    char s[N]; 
    struct mat{
        int r, c;
        ll s[N][N];
        mat(int r=0,int c=0):r(r),c(c){
            memset(s, 0, sizeof s);
        }
    };
    mat operator*(const mat&a, const mat&b){
        mat c = mat(a.r, b.c);
        for (int i = 0; i < c.r;i++){
            for (int j = 0; j < c.c;j++)
                for (int k = 0; k < a.c;k++)
                    c.s[i][j] = (c.s[i][j] + a.s[i][k] * b.s[k][j]) % mod;
        }
        return c;
    }
    mat power(mat a,int b){
        mat res = a;
        b--;
        for (; b;b>>=1){
            if(b & 1)
                res = res * a;
            a = a * a;
        }
        return res;
    }
    inline int xid(char ch){
        if(ch == 'A') return 0;
        if(ch == 'C') return 1;
        if(ch == 'G') return 2;
        if(ch == 'T') return 3;
    }
    namespace AC{
        int tr[N][4],tot;
        int e[N],fail[N];
        queue<int> q;
        void init(){
            for(int i=0;i<=tot;i++){
                memset(tr[i],0,sizeof tr[i]);
                fail[i] = e[i] = 0;
            }
            tot = 0;
        }
        void insert(char *s){
            int u = 0;
            int len = strlen(s + 1);
            for(int i=1;i<=len;i++){
                int c = xid(s[i]);
                if(!tr[u][c]){
                    tr[u][c] = ++tot;
                }
                u = tr[u][c];
            }
            e[u] ++;
        }
        void build(){
            for(int i=0;i<4;i++)if(tr[0][i])q.push(tr[0][i]);
            while(q.size()){
                int u = q.front();q.pop();
                e[u] += e[fail[u]];
                for(int i=0;i<4;i++){
                    if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                    else tr[u][i] = tr[fail[u]][i];
                }
            }
        }
        void solve(){
            mat t(tot+1, tot+1);
            for(int i=0;i<=tot;i++){
                if(e[i]) continue;
                for(int j=0;j<4;j++){
                    int u = tr[i][j];
                    if(e[u]) continue;
                    t.s[i][u] ++;
                }
            }
            t = power(t, n);
            int res = 0;
            for(int i=0;i<=tot;i++) res = (res + t.s[0][i]) % mod;
            printf("%d
    ", res);
        }
    }
    int main(){
        scanf("%d%d",&m,&n);
        for(int i=1;i<=m;i++){
            scanf("%s", s+1);
            AC::insert(s);
        }
        AC::build();
        AC::solve();
        return 0;
    }
    

    15. 考研路茫茫——单词情结

    HDU - 2243

    和上个题基本一样

    const int N = 30 + 5;
    int m;
    ll n;
    char s[N]; 
    struct mat{
       int r, c;
       ull s[N][N];
       mat(int r=0,int c=0):r(r),c(c){
           memset(s, 0, sizeof s);
       }
       void print(){
           for(int i=0;i<r;i++){
               for(int j=0;j<c;j++){
                   printf("%llu ", s[i][j]);
               }
               puts("");
           }
       }
    }one;
    mat operator*(const mat&a, const mat&b){
       mat c = mat(a.r, b.c);
       for (int i = 0; i < c.r;i++){
           for (int j = 0; j < c.c;j++)
               for (int k = 0; k < a.c;k++)
                   c.s[i][j] = c.s[i][j] + a.s[i][k] * b.s[k][j];
       }
       return c;
    }
    mat operator+(const mat&a, const mat&b){
       mat c = mat(a.r, b.c);
       for (int i = 0; i < c.r;i++){
           for (int j = 0; j < c.c;j++){
               c.s[i][j] = a.s[i][j] + b.s[i][j];
           }
       }
       return c;
    }
    mat power(mat a,ll b){
       mat res = a;
       b--;
       for (; b;b>>=1){
           if(b & 1)
               res = res * a;
           a = a * a;
       }
       return res;
    }
    mat getsum2(mat a, ll b){
       if(b == 0) return one;
       if(b == 1) return a;
       if(b & 1){
           return (one + power(a, (b+1)/2)) * getsum2(a, b / 2) + power(a, (b+1)/2);
       }
       else{
           return (one + power(a, b/2)) * getsum2(a, b / 2);
       }
    }
    ull ksm(ull a, ull b){
       ull res = 1;
       for(;b;b>>=1){
           if(b & 1) res = res * a;
           a = a * a;
       }
       return res;
    }
    ull getsum(ull a, ull b){
       if(b == 0) return 1;
       if(b == 1) return a;
       if(b & 1){
           return (ksm(a, (b+1)/2) + 1) * getsum(a, b / 2) + ksm(a, (b+1)/2);
       }
       else{
           return (ksm(a, b/2) + 1) * getsum(a, b / 2);
       }
    }
    namespace AC{
       int tr[N][26],tot;
       int e[N],fail[N];
       queue<int> q;
       void init(){
           for(int i=0;i<=tot;i++){
               memset(tr[i],0,sizeof tr[i]);
               fail[i] = e[i] = 0;
           }
           tot = 0;
       }
       void insert(char *s){
           int u = 0;
           int len = strlen(s + 1);
           for(int i=1;i<=len;i++){
               int c = s[i] - 'a';
               if(!tr[u][c]){
                   tr[u][c] = ++tot;
               }
               u = tr[u][c];
           }
           e[u] ++;
       }
       void build(){
           for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
           while(q.size()){
               int u = q.front();q.pop();
               e[u] += e[fail[u]];
               for(int i=0;i<26;i++){
                   if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                   else tr[u][i] = tr[fail[u]][i];
               }
           }
       }
       void solve(){
           mat t(tot+1, tot+1);
           one = mat(tot+1, tot+1);
           for(int i=0;i<=tot;i++)one.s[i][i] = 1;
           for(int i=0;i<=tot;i++){
               if(e[i]) continue;
               for(int j=0;j<26;j++){
                   int u = tr[i][j];
                   if(e[u]) continue;
                   t.s[i][u] ++;
               }
           }
           t = getsum2(t, n);
           ull res = 0;
           for(int i=0;i<=tot;i++) res += t.s[0][i];
           printf("%llu
    ", getsum(26, n) - res);
       }
    }
    int main(){
       while(~scanf("%d%lld",&m,&n)){
           AC::init();
           for(int i=1;i<=m;i++){
               scanf("%s", s+1);
               AC::insert(s);
           }
           AC::build();
           AC::solve();
       }
       return 0;
    }
    

    16. Censored!

    POJ - 1625

    d[i][j] 表示长度为 i,到达 j 号节点的状态

    if(e[u] == 0){
        d[i+1][u] += d[i][j];// u = tr[j][c];
    }
    
    1. 输入的字符不太正常,应该是会超过127导致出现负数
    2. 没取模,应该用大数
    #include <cstdio>
    #include <iostream>
    #include <cmath>
    #include <string>
    #include <cstring>
    #include <algorithm>
    #include <vector>
    #include <queue>
    using namespace std;
    typedef long long ll;
    const int inf = 0x3f3f3f3f;
    #define dbg(x...) do { cout << "33[32;1m" << #x <<" -> "; err(x); } while (0)
    void err() { cout << "33[39;0m" << endl; }
    template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
    const int N = 100 + 5;
    const int base = 10;
    int n, m, p;
    int xid[300];
    char s[N], a[N];
    int get(char ch){
        for(int i=1;i<=m;i++){
            if(ch == a[i]) return i-1;
        }
    }
    struct BigInt
    {
        int v[105], len;
        BigInt(int r = 0)
        {
            memset(v, 0, sizeof(v));
            for(len = 0; r > 0; r /= base) v[len++] = r % base;
        }
        BigInt operator + (const BigInt &a)
        {
            BigInt ans;
            int i , c = 0;
            for(i = 0; i < len || i < a.len || c > 0; i++)
            {
                if(i < len)c += v[i];
                if(i < a.len)c += a.v[i];
                ans.v[i] = c % base;
                c /= base;
            }
            ans.len = i;
            return ans;
        }
        void print()
        {
            printf("%d", len == 0 ? 0 : v[len - 1]);
            for(int i = len - 2; i >= 0; i--)
                printf("%d", v[i]);
            printf("
    ");
        }
    };
    namespace AC{
        int tr[N][55],tot;
        int e[N],fail[N];
        BigInt d[55][N];
        int vis[55][N];
        queue<int> q;
        void init(){
            for(int i=0;i<=tot;i++){
                memset(tr[i],0,sizeof tr[i]);
                fail[i] = e[i] = 0;
            }
            tot = 0;
        }
        void insert(char *s){
            int u = 0;
            int len = strlen(s + 1);
            for(int i=1;i<=len;i++){
                int c = get(s[i]);
                if(!tr[u][c]){
                    tr[u][c] = ++tot;
                }
                u = tr[u][c];
            }
            e[u] ++;
        }
        void build(){
            for(int i=0;i<m;i++)if(tr[0][i])q.push(tr[0][i]);
            while(q.size()){
                int u = q.front();q.pop();
                e[u] += e[fail[u]];
                for(int i=0;i<m;i++){
                    if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                    else tr[u][i] = tr[fail[u]][i];
                }
            }
        }
        void solve(){
            d[0][0] = BigInt(1);
            for(int i = 0;i < n; i++){
                for(int u = 0; u <= tot; u++){
                    if(e[u]) continue;
                    for(int c = 0; c < m; c++){
                        int v = tr[u][c];
                        if(!e[v]){
                            d[i+1][v] = d[i+1][v] + d[i][u];
                        }
                    }
                }
            }
            BigInt res;
            for(int i=0;i<=tot;i++){
                res =  res + d[n][i];
            }
            res.print();
        }
    }
    int main(){
        scanf("%d%d%d",&m,&n,&p);
        scanf("%s", a+1);
        for(int i=1;i<=p;i++){
            scanf("%s", s+1);
            AC::insert(s);
        }
        AC::build();
        AC::solve();
        return 0;
    }
    

    17. Walk Through Squares

    HDU - 4758

    d[u][i][j][k] 表示走到 (u) 节点,走了$ i$ 次 'D', (j) 次 'R', 经过的模板串状态为 $k$时的方案数

    const int N = 200 + 5;
    const int mod = 1000000007;
    int n, m;
    char s[N];
    inline xid(char ch){
        if(ch == 'R') return 1;
        else return 0;
    }
    namespace AC{
        int tr[N][2],tot;
        int e[N],fail[N];
        int d[N][105][105][4];
        bool vis[N][105][105][4];
        queue<int> q;
        void init(){
            for(int i=0;i<=tot;i++){
                memset(tr[i],0,sizeof tr[i]);
                fail[i] = e[i] = 0;
            }
            tot = 0;
        }
        void insert(char *s, int id){
            int u = 0;
            int len = strlen(s + 1);
            for(int i=1;i<=len;i++){
                int c = xid(s[i]);
                if(!tr[u][c]){
                    tr[u][c] = ++tot;
                }
                u = tr[u][c];
            }
            e[u] |= 1 << id;
        }
        void build(){
            for(int i=0;i<2;i++)if(tr[0][i])q.push(tr[0][i]);
            while(q.size()){
                int u = q.front();q.pop();
                e[u] |= e[fail[u]];
                for(int i=0;i<2;i++){
                    if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                    else tr[u][i] = tr[fail[u]][i];
                }
            }
        }
        void solve(){
            memset(d, 0, sizeof d);
            memset(vis, 0, sizeof vis);
            d[0][0][0][0] = 1;
            vis[0][0][0][0] = 1;
            for(int i=0; i <= n; i++){
                for(int j=0; j<= m; j++){
                    for(int u = 0; u<= tot; u++){
                        for(int k = 0; k < 4; k++){
                            if(vis[u][i][j][k] == 0) continue; // 如果该点压根就到不了,就不要参与转移
                            if(i < n){
                                int v = tr[u][0];
                                vis[v][i + 1][j][k | e[v]] = 1;
                                (d[v][i + 1][j][k | e[v]] += d[u][i][j][k]) %= mod;
                            }
                            if(j < m){
                                int v = tr[u][1];
                                vis[v][i][j + 1][k | e[v]] = 1;
                                (d[v][i][j + 1][k | e[v]] += d[u][i][j][k]) %= mod;
                            }
                        }
                    }
                }
            }
            ll res = 0;
            for(int i=0; i<=tot; i++){
                (res += d[i][n][m][3]) %= mod;
            }
            printf("%lld
    ", res);
        }
    }
    
    int main(){
        int T;
        scanf("%d",&T);
        while(T--){
            scanf("%d%d", &m, &n);
            AC::init();
            scanf("%s", s+1);
            AC::insert(s, 0);
            scanf("%s", s+1);
            AC::insert(s, 1);
            AC::build();
            AC::solve();
        }
        return 0;
    }
    
  • 相关阅读:
    第1课 Git、谁与争锋
    程序员最真实的10个瞬间
    程序员最真实的10个瞬间
    一文读懂前端缓存
    一文读懂前端缓存
    一文读懂前端缓存
    EF使用CodeFirst创建数据库和表
    EF使用CodeFirst创建数据库和表
    EF使用CodeFirst创建数据库和表
    ASP.NET MVC的过滤器笔记
  • 原文地址:https://www.cnblogs.com/1625--H/p/12532624.html
Copyright © 2020-2023  润新知