AC自动机 从入门到模板

前言

作为一名菜鸡选手,我的目的仅仅是初步了解ac自动机的原理和各种性质。。。

正篇

1.AC自动机的基本性质

AC自动机的前置技能点 KMP,字典树
ac自动机的原理解释可以参考
hihocode hiho一下
我解释不清楚
1。跟后缀自动机不一样,ac自动机,是一张有向有环图,后缀自动机是有向无环图。
2。ac自动机是多对一的多模式串匹配。
3。ac自动机的常用的属性有 下面几个

1.val[i] 记录编号为i的节点的性质,一般用于记录,是否是一个模式串的终点
2.last[i] 记录编号为i的节点后缀链接,既 如果last[i] 不为 0 说明编号last[i]的节点为一个模式串,且是编号为i的字符串的最长后缀。(跟后缀自动机的后缀链接是一个东西)
3. fail[i] fail指针,跟kmp一样 表示编号为i的节点失配后,应该转移到哪一个状态。
4. ch[i][j] 状态转移方程 表示 编号为i的节点 下一个字符接上j的话,应该匹配到哪一个状态。

2.AC自动机的构建步骤

  1. 建立字典树。
    对于val数组 ,一般在建字典树的时候就要处理完成。 对于ch数组,在建字典树的时候会‘’初步‘’处理完成。*这时候ch表示的是一颗树

  2. 构建fail指针
    原理,我讲了也没(wo)人)(ye)听(bu)的(hui)懂。
    在构建玩fail指针后,会构建出上述的常用属性。并且会将字典树补成一张有向有环图。
    这个时候ch已经不再是字典树当中的那种定义了,ch[i][j]它表示编号为i的节点能匹配到的最优位置。

然后,就可以结合题意 ,运用ac自动机的属性来解决问题了。

3.实践

AC自动机的题目,其实就是对trie图的属性运用。
下面,列出一些题目,介绍一下ac自动机的常见用法。
1. hdu2222
ac自动机模板题,随便都能过,唯一可以注意的是,因为题目要求的是求字符串匹配,所以暴力往回跳的时候,可以不用fail指针往回跳,直接使用last既可,复杂度O(len * N) 但实际上不可能有这么多,

#include 

#ifdef LOCAL
#define debug(x) cout<<#x<<" = "<<(x)<
#else
#define debug(x) 1;
#endif

#define lson id<<1,l,mid
#define rson id<<1|1,mid+1,r
#define lowbit(x) x&-x
#define mp make_pair
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int MOD = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const ll INFLL = 0x3f3f3f3f3f3f3f3fll;
const int MAXN = 2e6 + 5;
const int maxn = 2e6 + 100;


const int SIGMA_SIZE = 26;
const int MAXNODE = 100000 * 50;
const int MAXS = 150 + 10;
struct ACautomata {

    int ch[MAXNODE][SIGMA_SIZE];
    int f[MAXNODE];    // fail函数
    int val[MAXNODE];  // 每个字符串的结尾结点都有一个非0的val
    int last[MAXNODE]; // 输出链表的下一个结点
    int sz;
    int d[MAXNODE];
    void init() {
        sz = 1;
        memset (ch[0], 0, sizeof (ch[0]) );
        memset(d, 0, sizeof(d));
    }

    // 字符c的编号
    inline int idx (char c) {
        return c - 'a';
    }

    // 插入字符串。v必须非0
    void insert (char *s) {
        int u = 0, n = strlen (s);
        for (int i = 0; i < n; i++) {
            int c = idx (s[i]);
             //printf("%c", s[i]);
            if (!ch[u][c]) {
                memset (ch[sz], 0, sizeof (ch[sz]) );
                val[sz] = 0;
                ch[u][c] = sz++;
            }
            u = ch[u][c];
            //printf("%d", u);
            //puts("");
        }
        val[u] += 1;
    }

    // 递归打印匹配文本串str[i]结尾的后缀,以结点j结尾的所有字符串
    void print (int i, int j) {
        if (j) {
            print (i, last[j]);
        }
    }

    // 在T中找模板
    void find (char* T) {
        int n = strlen (T);
        int j = 0; // 当前结点编号,初始为根结点
        for (int i = 0; i < n; i++) { // 文本串当前指针
            int c = idx (T[i]);
            j = ch[j][c];
            if (val[j]) print (i, j);
            else if (last[j]) print (i, last[j]); // 找到了!
        }
    }

    // 计算fail函数
    void getFail() {
        queue<int> q;
        f[0] = 0;
        // 初始化队列
        for (int c = 0; c < SIGMA_SIZE; c++) {
            int u = ch[0][c];
            if (u) {
                f[u] = 0;
                q.push (u);
                last[u] = 0;
            }
        }
        // 按BFS顺序计算fail
        while (!q.empty() ) {
            int r = q.front();
            q.pop();
            for (int c = 0; c < SIGMA_SIZE; c++) {
                int u = ch[r][c];
                if (!u) {
                    ch[r][c] = ch[f[r]][c];
                    continue;
                }
                q.push (u);
                int v = f[r];
                while (v && !ch[v][c]) v = f[v];
                f[u] = ch[v][c];
                last[u] = val[f[u]] ? f[u] : last[f[u]];
            }
        }
    }
    int query(char *s) {
        int len =strlen(s);
        int now = 0;
        int ans =0;
        for(int i=0; i'a'];
            int tmp = now;
            while(now) {
                if(val[now]) {
                    ans+=val[now];
                    val[now]=0;
                }
                now = last[now];
            }
            now =tmp;
        }
        return ans;
    }
} ac;

char s[MAXN];



int main() {
    int n;
    int T;
    cin>>T;
    while(T--) {
        cin >> n;
        ac.init();
        for (int i = 1; i <= n; i++) {
            scanf ("%s", s);
            ac.insert (s);
        }
        ac.getFail();
        scanf ("%s", s );
        //ac.find(s);
        printf("%d\n",ac.query(s));
    }
    return 0;
}

2.
HDU - 2896
同样的模板题,可以注意的地方跟hdu2222 是一样的。

#include 

#ifdef LOCAL
#define debug(x) cout<<#x<<" = "<<(x)<
#else
#define debug(x) 1;
#endif

#define lson id<<1,l,mid
#define rson id<<1|1,mid+1,r
#define lowbit(x) x&-x
#define mp make_pair
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int MOD = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const ll INFLL = 0x3f3f3f3f3f3f3f3fll;
const int MAXN = 1e5;
const int maxn = 2e6 + 100;


const int SIGMA_SIZE = 130;
const int MAXNODE = 1e5+10;
const int MAXS = 150 + 10;
struct ACautomata {

    int ch[MAXNODE][SIGMA_SIZE];
    int f[MAXNODE];    // fail函数
    int val[MAXNODE];  // 每个字符串的结尾结点都有一个非0的val
    int last[MAXNODE]; // 输出链表的下一个结点
    int sz;
    //int d[MAXNODE];
    void init() {
        sz = 1;
        memset (ch[0], 0, sizeof (ch[0]) );
        //memset(d, 0, sizeof(d));
    }

    // 字符c的编号
    inline int idx (char c) {
        return (int)c;
    }

    // 插入字符串。v必须非0
    void insert (char *s,int ids) {
        int u = 0, n = strlen (s);
        for (int i = 0; i < n; i++) {
            int c = idx (s[i]);
             //printf("%c", s[i]);
            if (!ch[u][c]) {
                memset (ch[sz], 0, sizeof (ch[sz]) );
                val[sz] = 0;
                ch[u][c] = sz++;
            }
            u = ch[u][c];
        }
        val[u] =ids;
    }

    // 递归打印匹配文本串str[i]结尾的后缀,以结点j结尾的所有字符串
    void print (int i, int j) {
        if (j) {
            print (i, last[j]);
        }
    }

    // 在T中找模板
    void find (char* T) {
        int n = strlen (T);
        int j = 0; // 当前结点编号,初始为根结点
        for (int i = 0; i < n; i++) { // 文本串当前指针
            int c = idx (T[i]);
            j = ch[j][c];
            if (val[j]) print (i, j);
            else if (last[j]) print (i, last[j]); // 找到了!
        }
    }

    // 计算fail函数
    void getFail() {
        queue<int> q;
        f[0] = 0;
        // 初始化队列
        for (int c = 0; c < SIGMA_SIZE; c++) {
            int u = ch[0][c];
            if (u) {
                f[u] = 0;
                q.push (u);
                last[u] = 0;
            }
        }
        // 按BFS顺序计算fail
        while (!q.empty() ) {
            int r = q.front();
            q.pop();
            for (int c = 0; c < SIGMA_SIZE; c++) {
                int u = ch[r][c];
                if (!u) {
                    ch[r][c] = ch[f[r]][c];
                    continue;
                }
                q.push (u);
                int v = f[r];
                while (v && !ch[v][c]) v = f[v];
                f[u] = ch[v][c];
                last[u] = val[f[u]] ? f[u] : last[f[u]];
            }
        }
    }
    void query(char *s,vector<int> &V) {
        V.clear();
        int len =strlen(s);
        int now = 0;
        int ans =0;
        for(int i=0; iint)s[i]];
            int tmp = now;
            while(now) {
                if(val[now]) {
                   V.push_back(val[now]);
                }
                now = f[now];
            }
            now =tmp;
        }
    }
} ac;

char s[MAXN];



int main() {
    int n;
    while(cin>>n) {
        ac.init();
        for (int i = 1; i <= n; i++) {
            scanf ("%s", s);
            ac.insert (s,i);
        }
        ac.getFail();
        cin>>n;
        int tot=0;
        vector<int> V;
        for(int i=0;iscanf ("%s",s);
            ac.query(s,V);
            if(V.size()){
                sort(V.begin(),V.end());
                V.erase(unique(V.begin(),V.end()),V.end());
                tot++;
                printf("web %d:",i+1);
                for(int i=0;iprintf(" %d",V[i]);
                }
                puts("");
            }
        }
        printf("total: %d\n",tot);
    }
    return 0;
}

3.
HDU-3065
模板题

#include 

#ifdef LOCAL
#define debug(x) cout<<#x<<" = "<<(x)<
#else
#define debug(x) 1;
#endif

#define lson id<<1,l,mid
#define rson id<<1|1,mid+1,r
#define lowbit(x) x&-x
#define mp make_pair
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int MOD = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const ll INFLL = 0x3f3f3f3f3f3f3f3fll;
const int MAXN = 1e5;
const int maxn = 2e6 + 100;


const int SIGMA_SIZE = 130;
const int MAXNODE =101000;
const int MAXS = 1005;
char str[1005][100];
char s[2000005];
int ans[10000];
struct ACautomata {

    int ch[MAXNODE][SIGMA_SIZE];
    int f[MAXNODE];    // fail函数
    int val[MAXNODE];  // 每个字符串的结尾结点都有一个非0的val
    int last[MAXNODE]; // 输出链表的下一个结点
    int sz;
    //int d[MAXNODE];
    void init() {
        sz = 1;
        memset (ch[0], 0, sizeof (ch[0]) );
        memset(ans,0,sizeof ans);
        //memset(d, 0, sizeof(d));
    }

    // 字符c的编号
    inline int idx (char c) {
        return (int)c;
    }

    // 插入字符串。v必须非0
    void insert (char *s,int ids) {
        int u = 0, n = strlen (s);
        for (int i = 0; i < n; i++) {
            int c = idx (s[i]);
             //printf("%c", s[i]);
            if (!ch[u][c]) {
                memset (ch[sz], 0, sizeof (ch[sz]) );
                val[sz] = 0;
                ch[u][c] = sz++;
            }
            u = ch[u][c];
        }
        val[u] =ids;
    }

    // 递归打印匹配文本串str[i]结尾的后缀,以结点j结尾的所有字符串
    void print (int i, int j) {
        if (j) {
            print (i, last[j]);
        }
    }

    // 在T中找模板
    void find (char* T) {
        int n = strlen (T);
        int j = 0; // 当前结点编号,初始为根结点
        for (int i = 0; i < n; i++) { // 文本串当前指针
            int c = idx (T[i]);
            j = ch[j][c];
            if (val[j]) print (i, j);
            else if (last[j]) print (i, last[j]); // 找到了!
        }
    }

    // 计算fail函数
    void getFail() {
        queue<int> q;
        f[0] = 0;
        // 初始化队列
        for (int c = 0; c < SIGMA_SIZE; c++) {
            int u = ch[0][c];
            if (u) {
                f[u] = 0;
                q.push (u);
                last[u] = 0;
            }
        }
        // 按BFS顺序计算fail
        while (!q.empty() ) {
            int r = q.front();
            q.pop();
            for (int c = 0; c < SIGMA_SIZE; c++) {
                int u = ch[r][c];
                if (!u) {
                    ch[r][c] = ch[f[r]][c];
                    continue;
                }
                q.push (u);
                int v = f[r];
                while (v && !ch[v][c]) v = f[v];
                f[u] = ch[v][c];
                last[u] = val[f[u]] ? f[u] : last[f[u]];
            }
        }
    }
    void query(char *s) {
        int len =strlen(s);
        int now = 0;
        for(int i=0; iint tmp = now;
            while(now) {
                if(val[now]) {
                   ans[val[now]]++;
                }
                now = last[now];
            }
            now =tmp;
        }
    }
} ac;
int main() {
    int n;
    while(cin>>n) {
        ac.init();
        for (int i = 1; i <= n; i++) {
            scanf ("%s", str[i]);
            ac.insert (str[i],i);
        }
        ac.getFail();
        scanf("%s",s);
        ac.query(s);
        for(int i=1;i<=n;i++){
            if(!ans[i]) continue;
            printf("%s: %d\n",str[i],ans[i]);
        }
    }
    return 0;
}

4.
poj-2778
这题还是很有意思滴,利用里AC自动机的性质,因为ac自动机是一张图,任意两个点之间右边,就说明他们两个一定有一个是另一个的子串或者是后缀。 利用这个建立邻接矩阵,跑矩阵快速幂即可。

#include 
#include 
#include 
#include 
#include 
#define lson id<<1,l,mid
#define rson id<<1|1,mid+1,r
#define lowbit(x) x&-x
#define mp make_pair
#define pb push_back
using namespace std;
typedef long long ll;
typedef long long LL;
typedef pair<int, int> pii;
const int MOD = 100000;
const int INF = 0x3f3f3f3f;
const ll INFLL = 0x3f3f3f3f3f3f3f3fll;
const int MAXN = 1e5;
const int maxn = 2e6 + 100;


const int SIGMA_SIZE = 130;
const int MAXNODE =101000;
const int MAXS = 1005;
char str[1005][100];
char s[2000005];
int ans[10000];
int maps[300];
struct ACautomata {

    int ch[MAXNODE][SIGMA_SIZE];
    int f[MAXNODE];    // fail函数
    int val[MAXNODE];  // 每个字符串的结尾结点都有一个非0的val
    int last[MAXNODE]; // 输出链表的下一个结点
    int sz;
    //int d[MAXNODE];
    void init() {
        sz = 1;
        memset (ch[0], 0, sizeof (ch[0]) );
        //memset(d, 0, sizeof(d));
    }

    // 字符c的编号
    inline int idx (char c) {
        return maps[c];
    }

    // 插入字符串。v必须非0
    void insert (char *s,int ids) {
        int u = 0, n = strlen (s);
        for (int i = 0; i < n; i++) {
            int c = idx (s[i]);
            //printf("%c", s[i]);
            if (!ch[u][c]) {
                memset (ch[sz], 0, sizeof (ch[sz]) );
                val[sz] = 0;
                ch[u][c] = sz++;
            }
            u = ch[u][c];
        }
        val[u] =ids;
    }

    // 递归打印匹配文本串str[i]结尾的后缀,以结点j结尾的所有字符串
    void print (int i, int j) {
        if (j) {
            print (i, last[j]);
        }
    }

    // 在T中找模板
    void find (char* T) {
        int n = strlen (T);
        int j = 0; // 当前结点编号,初始为根结点
        for (int i = 0; i < n; i++) { // 文本串当前指针
            int c = idx (T[i]);
            j = ch[j][c];
            if (val[j]) print (i, j);
            else if (last[j]) print (i, last[j]); // 找到了!
        }
    }

    // 计算fail函数
    void getFail() {
        queue<int> q;
        f[0] = 0;
        // 初始化队列
        for (int c = 0; c < SIGMA_SIZE; c++) {
            int u = ch[0][c];
            if (u) {
                f[u] = 0;
                q.push (u);
                last[u] = 0;
            }
        }
        // 按BFS顺序计算fail
        while (!q.empty() ) {
            int r = q.front();
            q.pop();
            for (int c = 0; c < SIGMA_SIZE; c++) {
                int u = ch[r][c];
                if (!u) {
                    ch[r][c] = ch[f[r]][c];
                    continue;
                }
                q.push (u);
                int v = f[r];
                while (v && !ch[v][c]) v = f[v];
                f[u] = ch[v][c];
                last[u] = val[f[u]] ? f[u] : last[f[u]];
            }
        }
    }
    void query(char *s) {
        int len =strlen(s);
        int now = 0;
        for(int i=0; iint tmp = now;
            while(now) {
                if(val[now]) {
                    ans[val[now]]++;
                }
                now = last[now];
            }
            now =tmp;
        }
    }
} ac;
//<---------------------quickpow----------------------->
const int matX = 1e2 + 5;
const int mod = 100000;
struct Matrix {
    int n, m, s[matX][matX];
    Matrix(int n, int m): n(n), m(n) {
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < n; j++) s[i][j] = 0;
            //s[i][i]=1;
        }
    }
    Matrix operator*(const Matrix &P)const {
        Matrix ret(n, P.m);
        for(int i = 0; i < n; i++) {
            for(int k = 0; k < m; k++) {
                if(s[i][k]) {
                    for(int j = 0; j < P.m; j++) {
                        ret.s[i][j] = ((LL)s[i][k] * P.s[k][j] + ret.s[i][j]) % mod;
                    }
                }
            }
        }
        return ret;
    }
    Matrix operator^(const LL &P)const {
        LL num = P;
        Matrix ret(n, m), tmp = *this;
        for(int i = 0; i < n; i++) ret.s[i][i] = 1;
        while(num) {
            if(num & 1) ret = ret * tmp;
            tmp = tmp * tmp;
            num >>= 1;
        }
        return ret;
    }
};
int main() {
    maps['A']=0;
    maps['C']=1;
    maps['T']=2;
    maps['G']=3;
    long long n,m;
    while(cin>>n>>m) {
        ac.init();
        for (int i = 1; i <= n; i++) {
            scanf ("%s", str[i]);
            ac.insert (str[i],1);
        }
        ac.getFail();
        Matrix E(ac.sz,ac.sz);
        for(int i=0; i<=ac.sz; i++) {
            if(ac.val[i] || ac.last[i]) {
                    continue;
            }
            for(int j=0; j<4; j++) {
                //if(!ac.ch[i][j]) continue;
                if(ac.val[ac.ch[i][j]] || ac.last[ac.ch[i][j]]) {
                    continue;
                }
                E.s[i][ac.ch[i][j]]++;
            }
        }
        E=E^m;

        long long ans =0 ;
        for(int i=0; i0][i];
            ans%=MOD;
        }
        cout<return 0;
}

5.HDU-2825
这题我觉得也很有意思。
要求长度为n的串中,包含不少于k种给出模式串的方案数。
最多给出m个模式串,m<=10
观察到m很小,应该要很轻松的联想的状压。
然后吧Tire图建出来 , 跑一个简单DP即可。
dp[26][MAXNODE][1050];// dp[i][j][k] 表示 第i个数,到达第j个状态,包含k个子串的方案数

#include 
#include 
#include 
#include 
#include 
#define lson id<<1,l,mid
#define rson id<<1|1,mid+1,r
#define lowbit(x) x&-x
#define mp make_pair
#define pb push_back
using namespace std;
typedef long long ll;
typedef long long LL;
typedef pair<int, int> pii;
const int MOD = 20090717;
const int INF = 0x3f3f3f3f;
const ll INFLL = 0x3f3f3f3f3f3f3f3fll;
const int SIGMA_SIZE = 30;
const int MAXNODE =255;
const int MAXS = 1005;
char str[1005];
char s[2000005];
int ans[10000];
int maps[300];
struct ACautomata {

    int ch[MAXNODE][SIGMA_SIZE];
    int f[MAXNODE];    // fail函数
    int val[MAXNODE];  // 每个字符串的结尾结点都有一个非0的val
    int last[MAXNODE]; // 输出链表的下一个结点
    int sz;
    //int d[MAXNODE];
    void init() {
        sz = 1;
        memset (ch[0], 0, sizeof (ch[0]) );
        //memset(d, 0, sizeof(d));
    }

    // 字符c的编号
    inline int idx (char c) {
        return c-'a';
    }

    // 插入字符串。v必须非0
    void insert (char *s,int ids) {
        int u = 0, n = strlen (s);
        for (int i = 0; i < n; i++) {
            int c = idx (s[i]);
            //printf("%c", s[i]);
            if (!ch[u][c]) {
                memset (ch[sz], 0, sizeof (ch[sz]) );
                val[sz] = 0;
                ch[u][c] = sz++;
            }
            u = ch[u][c];
        }
        val[u] =ids;
    }

    // 递归打印匹配文本串str[i]结尾的后缀,以结点j结尾的所有字符串
    void print (int i, int j) {
        if (j) {
            print (i, last[j]);
        }
    }

    // 在T中找模板
    void find (char* T) {
        int n = strlen (T);
        int j = 0; // 当前结点编号,初始为根结点
        for (int i = 0; i < n; i++) { // 文本串当前指针
            int c = idx (T[i]);
            j = ch[j][c];
            if (val[j]) print (i, j);
            else if (last[j]) print (i, last[j]); // 找到了!
        }
    }

    // 计算fail函数
    void getFail() {
        queue<int> q;
        f[0] = 0;
        // 初始化队列
        for (int c = 0; c < SIGMA_SIZE; c++) {
            int u = ch[0][c];
            if (u) {
                f[u] = 0;
                q.push (u);
                last[u] = 0;
            }
        }
        // 按BFS顺序计算fail
        while (!q.empty() ) {
            int r = q.front();
            q.pop();
            for (int c = 0; c < SIGMA_SIZE; c++) {
                int u = ch[r][c];
                if (!u) {
                    ch[r][c] = ch[f[r]][c];
                    continue;
                }
                q.push (u);
                int v = f[r];
                while (v && !ch[v][c]) v = f[v];
                f[u] = ch[v][c];
                last[u] = val[f[u]] ? f[u] : last[f[u]];
            }
        }
    }
    void query(char *s) {
        int len =strlen(s);
        int now = 0;
        for(int i=0; iint tmp = now;
            while(now) {
                if(val[now]) {
                    ans[val[now]]++;
                }
                now = last[now];
            }
            now =tmp;
        }
    }
} ac;
int dp[26][MAXNODE][1050];// dp[i][j][k] 表示 第i个数,到达第j个状态,包含k个子串的方案数
int num[2050];
int getsize(int a){
    int num=0;
    while(a){
        num+=a&1;
        a>>=1;
    }
    return num;
}
int main() {
    long long n,m,k;
    for(int i=0;i<2050;i++){
        num[i]=getsize(i);
    }
    while(cin>>n>>m>>k) {
        if(n==0 && m==0 && k==0) break;
        ac.init();
        for (int i = 1; i <= m; i++) {
            scanf ("%s", str);
            int lensssss = strlen(str);
            if(lensssss >n) continue;
            ac.insert (str,i);
        }
        ac.getFail();
        memset(dp,0,sizeof dp);
        int now=0;
        dp[0][0][0]=1;
        int v,tmp,nv;
        for(int i=0;ifor(int j=0;jfor(int ks = 0;ks<(1<if(!dp[i][j][ks]) continue;
                    for(int z=0;z<=25;z++){
                        v= ac.ch[j][z];

                        tmp = v;
                        nv=ks;
                        while(v){
                            if(ac.val[v])
                                nv|=(1<<(ac.val[v]-1));
                            //cout<
                            v=ac.last[v];
                        }
//                        if(nv==3){
//                            printf("dp[%d][%d][%d]=%lld\n",i,j,ks,dp[i][j][ks]);
//                        }
                        dp[i+1][tmp][nv]+=dp[i][j][ks];
                        dp[i+1][tmp][nv]%=MOD;
                    }
                }
            }
        }
        int ans =0;
        for(int j=0;jfor(int ks=0;ks<(1<if(num[ks]>=k)
                    ans+=dp[n][j][ks];
                ans %= MOD;
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

未完待续。。。。。。。。

你可能感兴趣的:(AC自动机)