Aho-Corasick automaton,该算法在1975年产生于贝尔实验室,是著名的多模式匹配算法之一。
KMP算法很好的解决了单模式匹配问题,如果有了字典树的基础,我们可以完美的结合二者解决多模式匹配问题。
在KMP算法中,我们预先根据待匹配串自身的信息得到失配指针,使得在每次匹配不成功后,可以不再去处理模式串的已匹配过的部分,进而使得复杂度降为O(N)。
对于多模式串匹配问题,当一个模式串与待匹配串不匹配时,失配指针可以指向任意一个串,这就需要我们利用字典树来组织所有模式串并得到失配指针。
构造失配指针的详细过程可参考 http://blog.csdn.net/niushuai666/article/details/7002823
时间复杂度分析:假设有N个长不超过m的模式串,待匹配串长度为L。建字典树的复杂度为O(Nm),对于每个L的前缀,最坏情况下匹配树高(m)次,故总的复杂度为O((N+L)*m)。
我的模板

1 /* 2 基于HDOJ 2222 的 AC自动机 3 文本串对多个模板串的查找 4 */ 5 #include<bits/stdc++.h> 6 using namespace std; 7 const int maxn = 555555; 8 const int alp =26; 9 10 int ch[maxn][alp], ed[maxn], fail[maxn]; 11 char str[1001000]; 12 13 int root, sz; 14 15 int newnode() 16 { 17 memset(ch[sz], -1, sizeof(ch[sz])); 18 ed[sz] = 0; 19 return sz++; 20 } 21 void init() 22 { 23 sz = 0; 24 root = newnode(); 25 } 26 void Insert(char str[]) 27 { 28 int len = strlen(str); 29 int now = root; 30 for(int i=0; i<len; i++) 31 { 32 int &tmp = ch[now][str[i]-'a']; 33 if(tmp == -1) tmp = newnode(); 34 now = tmp; 35 } 36 ed[now] ++; 37 } 38 39 40 void build() 41 { 42 queue<int> q; 43 fail[root] = root; 44 for(int i=0; i<alp; i++) 45 { 46 int &tmp = ch[root][i]; 47 if(tmp == -1) tmp = root; 48 else fail[tmp] = root, q.push(tmp); 49 } 50 while(!q.empty()) 51 { 52 int now = q.front(); q.pop(); 53 for(int i=0; i<alp; i++) 54 { 55 int &tmp = ch[now][i]; 56 if(tmp == -1) tmp = ch[fail[now]][i]; 57 else fail[tmp] = ch[fail[now]][i], q.push(tmp); 58 } 59 } 60 } 61 62 int query(char str[]) 63 { 64 int len = strlen(str); 65 int now = root; 66 int ret = 0; 67 for(int i=0; i<len; i++) 68 { 69 int tmp = now = ch[now][str[i]-'a']; 70 while(tmp != root) 71 { 72 if(ed[tmp] > 0) 73 ret += ed[tmp], 74 ed[tmp] = 0; 75 tmp = fail[tmp]; 76 } 77 } 78 return ret; 79 } 80 81 int main() 82 { 83 int n, t; 84 scanf("%d",&t); 85 while(t--) 86 { 87 scanf("%d",&n); 88 init(); 89 for(int i=1; i<=n; i++) 90 scanf("%s",str), Insert(str); 91 build(); 92 scanf("%s",str); 93 printf("%d ",query(str)); 94 } 95 return 0; 96 }
入门练习题 http://acm.hust.edu.cn/vjudge/contest/view.action?cid=96376#overview
1. hdu 2896 病毒侵袭
字典树的节点标记模式串的编号即可,注意字符的ASCALL码在128以内。

1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn = 111111; 4 const int alp = 128; 5 6 int ch[maxn][alp], ed[maxn], fail[maxn]; 7 char str[10100]; 8 9 int root, sz; 10 11 int newnode() 12 { 13 memset(ch[sz], -1, sizeof(ch[sz])); 14 ed[sz] = 0; 15 return sz++; 16 } 17 void init() 18 { 19 sz = 0; 20 root = newnode(); 21 } 22 void Insert(char str[], int tag) 23 { 24 int len = strlen(str); 25 int now = root; 26 for(int i=0; i<len; i++) 27 { 28 int &tmp = ch[now][str[i]]; 29 if(tmp == -1) tmp = newnode(); 30 now = tmp; 31 } 32 ed[now] = tag; 33 } 34 35 void build() 36 { 37 queue<int> q; 38 fail[root] = root; 39 for(int i=0; i<alp; i++) 40 { 41 int &tmp = ch[root][i]; 42 if(tmp == -1) tmp = root; 43 else fail[tmp] = root, q.push(tmp); 44 } 45 while(!q.empty()) 46 { 47 int now = q.front(); q.pop(); 48 for(int i=0; i<alp; i++) 49 { 50 int &tmp = ch[now][i]; 51 if(tmp == -1) tmp = ch[fail[now]][i]; 52 else fail[tmp] = ch[fail[now]][i], q.push(tmp); 53 } 54 } 55 } 56 57 bool vis[510]; 58 59 void query(char str[]) 60 { 61 int len = strlen(str); 62 int now = root; 63 int ret = 0; 64 for(int i=0; i<len; i++) 65 { 66 int tmp = now = ch[now][str[i]]; 67 while(tmp != root) 68 { 69 if(ed[tmp]) vis[ed[tmp]] = 1; 70 tmp = fail[tmp]; 71 } 72 } 73 } 74 75 int main() 76 { 77 int n, m; 78 while(scanf("%d",&n) == 1) 79 { 80 init(); 81 for(int i=1; i<=n; i++) 82 scanf("%s",str), Insert(str, i); 83 build(); 84 scanf("%d",&m); 85 int total = 0; 86 for(int i=1; i<=m; i++) 87 { 88 memset(vis, 0, sizeof(vis)); 89 scanf("%s",str); 90 query(str); 91 int a[5], id = 0; 92 for(int j=1; j<=n; j++) 93 if(vis[j]) a[++id] = j; 94 if(id) { 95 printf("web %d:",i); 96 for(int j=1; j<=id; j++) printf(" %d", a[j]); 97 puts(""); 98 total++; 99 } 100 } 101 printf("total: %d ",total); 102 } 103 return 0; 104 }
2. hdu 3065 病毒侵袭持续中
由于所找的串允许重叠,那就恰好是AC自动机模板了,在Trie上查找时,只要该节点有标记,加上即可。待匹配串的除大写字母以外的可见字符是没有用的,统一更改为'Z'+1就行了。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn = 111111; 4 const int alp = 27; 5 6 int ch[maxn][alp], ed[maxn], fail[maxn]; 7 char str[2000100]; 8 int num[1010]; 9 10 int root, sz; 11 12 int newnode() 13 { 14 memset(ch[sz], -1, sizeof(ch[sz])); 15 ed[sz] = 0; 16 return sz++; 17 } 18 void init() 19 { 20 sz = 0; 21 root = newnode(); 22 } 23 void Insert(char str[], int tag) 24 { 25 int len = strlen(str); 26 int now = root; 27 for(int i=0; i<len; i++) 28 { 29 int &tmp = ch[now][str[i]-'A']; 30 if(tmp == -1) tmp = newnode(); 31 now = tmp; 32 } 33 ed[now] = tag; 34 } 35 36 void build() 37 { 38 queue<int> q; 39 fail[root] = root; 40 for(int i=0; i<alp; i++) 41 { 42 int &tmp = ch[root][i]; 43 if(tmp == -1) tmp = root; 44 else fail[tmp] = root, q.push(tmp); 45 } 46 while(!q.empty()) 47 { 48 int now = q.front(); q.pop(); 49 for(int i=0; i<alp; i++) 50 { 51 int &tmp = ch[now][i]; 52 if(tmp == -1) tmp = ch[fail[now]][i]; 53 else fail[tmp] = ch[fail[now]][i], q.push(tmp); 54 } 55 } 56 } 57 58 void query(char str[]) 59 { 60 int len = strlen(str); 61 int now = root; 62 int ret = 0; 63 for(int i=0; i<len; i++) 64 { 65 int tmp = now = ch[now][str[i]-'A']; 66 while(tmp != root) 67 { 68 if(ed[tmp]) num[ed[tmp]] ++; 69 tmp = fail[tmp]; 70 } 71 } 72 } 73 char s[1010][55]; 74 int main() 75 { 76 int n, m; 77 while(scanf("%d",&n) == 1) 78 { 79 init(); 80 for(int i=1; i<=n; i++) 81 scanf("%s",s[i]), Insert(s[i], i), num[i]=0; 82 build(); 83 scanf("%s",str); 84 for(int i=0; str[i]; i++) 85 if(str[i]<'A' || str[i] >'Z') str[i] = 'Z'+1; 86 query(str); 87 for(int i=1; i<=n; i++) 88 if(num[i]) 89 printf("%s: %d ",s[i], num[i]); 90 } 91 return 0; 92 }
3. zoj 3430 Detect the Virus
恶心+AC自动机,以后写 4.poj 2778 DNA Sequence给N(N<=10)个长度不超过10的DNA串,问可以构造出多少个长度为M(M<=2000000000)的DNA串不含以上串,所有串仅由'A'、'C'、'G'、'T'四个字符构成。
对于一个有向图,我们要求一点x到另一点y的长度为L不同路径的个数,构造一个矩阵,令mat[i][j]表示直接相连i和j的边的数量,那么该矩阵的L次幂的mat[x,y]就是答案。
这里我们可以先构造出最多101个节点的Trie图,建立AC自动机,标记给出的串。对于每个节点u,并枚举每个以上字符,若该节点没被标记并且转移后的节点v也没有被标记,那么mat[u][v]++。求出pow(mat,M),矩阵第一行之和就是答案,因为它代表从root(空串)开始所有的长度为M的满足要求的所有串的数量和。
*注意,对于串"A"和"TAG",在建立自动机时,对于串"TAG"中的'A'节点也应该被标记,也就是说,每个节点的标记要考虑到失配节点的标记。

1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #include<queue> 7 typedef long long LL; 8 using namespace std; 9 const int maxn = 111111; 10 const int N = 110; 11 const int alp = 4; 12 const LL mod = 100000; 13 int ch[maxn][alp], ed[maxn], fail[maxn]; 14 char str[20]; 15 int Hash['z']; 16 int root, sz; 17 void mul(LL a[N][N], LL b[N][N]) 18 { 19 LL c[N][N] = {0}; 20 for(int k=0; k<sz; k++) 21 for(int i=0; i<sz; i++) 22 for(int j=0; j<sz; j++) 23 c[i][j] = (c[i][j] + a[i][k]*b[k][j]) % mod; 24 memcpy(a, c, sizeof(c)); 25 } 26 void quick_mod(LL a[N][N], int b) 27 { 28 LL c[N][N] = {0}; 29 for(int i=0; i<sz; i++) c[i][i] = 1; 30 while(b) 31 { 32 if(b&1) mul(c,a); 33 mul(a, a); 34 b>>=1; 35 } 36 memcpy(a, c, sizeof(c)); 37 } 38 39 int newnode() 40 { 41 memset(ch[sz], -1, sizeof(ch[sz])); 42 ed[sz] = 0; 43 return sz++; 44 } 45 void init() 46 { 47 sz = 0; 48 root = newnode(); 49 } 50 void Insert(char str[]) 51 { 52 int len = strlen(str); 53 int now = root; 54 for(int i=0; i<len; i++) 55 { 56 int &tmp = ch[now][Hash[str[i]]]; 57 if(tmp == -1) tmp = newnode(); 58 now = tmp; 59 } 60 ed[now] = 1; 61 } 62 63 void build() 64 { 65 queue<int> q; 66 fail[root] = root; 67 for(int i=0; i<alp; i++) 68 { 69 int &tmp = ch[root][i]; 70 if(tmp == -1) tmp = root; 71 else fail[tmp] = root, q.push(tmp); 72 } 73 while(!q.empty()) 74 { 75 int now = q.front(); q.pop(); 76 for(int i=0; i<alp; i++) 77 { 78 int &tmp = ch[now][i]; 79 if(tmp == -1) tmp = ch[fail[now]][i]; 80 else fail[tmp] = ch[fail[now]][i], ed[tmp] |= ed[fail[tmp]], q.push(tmp); 81 } 82 } 83 } 84 85 void get_matrix(LL a[N][N]) 86 { 87 for(int i=0; i<sz; i++) 88 { 89 if(ed[i]) continue; 90 for(int j=0; j<4; j++) 91 { 92 if(ed[ch[i][j]]) continue; 93 a[i][ch[i][j]] ++; 94 } 95 } 96 } 97 98 int main() 99 { 100 int n, m; 101 Hash['A'] = 0, Hash['C'] = 1, Hash['G'] = 2, Hash['T'] = 3; 102 while(scanf("%d %d",&n,&m) == 2) 103 { 104 init(); 105 for(int i=1; i<=n; i++) 106 { 107 scanf("%s",str); 108 Insert(str); 109 } 110 build(); 111 LL a[N][N] = {0}; 112 get_matrix(a); 113 quick_mod(a, m); 114 LL ans = 0; 115 for(int i=0; i<sz; i++) 116 ans =(ans + a[0][i]) % mod; 117 printf("%I64d ",ans); 118 } 119 return 0; 120 }
5.hdu 2243 考研路茫茫——单词情节
求长度不超过m的至少包含一个所给模式串的串的个数,并对2^64取模。
- 假设长度只能为m,上题中我们计算了不包含模式串的串的个数,而串的总数为pow(26,m),包含模式串的串的个数就是两者只差,这是很容易得出的。
- 那么如何求出所有长度不超过m的串的总数呢?
- 对于前者,我们对上题的矩阵的边长加1,并且置最后一列全为1,这样,矩阵的第一行之和就记录了所有情况之和,由于初始第一行最后一列为1,结果减一即可。
- 对于后者,令sum_pow(26,x)表示26^1+26^2+26^3+...+26^x, 则有,若x为奇数转化为26^x+sum_pow(26,x-1), 若为偶数则转化为sum_pow(26,x/2)*(26^(x/2)+1),这样可在log(x)内的时间复杂度内求出。
- 对于模2^64,只要将所有结果保存为unsigned long long 型,自然溢出就相当于对2^64取模。

1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #include<queue> 7 using namespace std; 8 typedef unsigned long long ull; 9 const int N = 50; 10 const int alp = 26; 11 const int maxn = 555555; 12 int ch[maxn][alp], ed[maxn], fail[maxn]; 13 int root, sz; 14 15 int newnode() 16 { 17 memset(ch[sz], -1, sizeof(ch[sz])); 18 ed[sz++] = 0; 19 return sz-1; 20 } 21 void init() 22 { 23 sz = 0; 24 root = newnode(); 25 } 26 27 void Insert(char str[]) 28 { 29 int len = strlen(str); 30 int now = root; 31 for(int i=0; i<len; i++) 32 { 33 int &tmp = ch[now][str[i]-'a']; 34 if(tmp == -1) tmp = newnode(); 35 now = tmp; 36 } 37 ed[now] = 1; 38 } 39 40 void build() 41 { 42 queue<int> q; 43 fail[root] = root; 44 for(int i=0; i<alp; i++) 45 { 46 int &tmp = ch[root][i]; 47 if(tmp == -1) tmp = root; 48 else fail[tmp] = root, q.push(tmp); 49 } 50 while(!q.empty()) 51 { 52 int now = q.front(); q.pop(); 53 for(int i=0; i<alp; i++) 54 { 55 int &tmp = ch[now][i]; 56 if(tmp == -1) tmp = ch[fail[now]][i]; 57 else fail[tmp] = ch[fail[now]][i], 58 ed[tmp] |= ed[fail[tmp]], 59 q.push(tmp); 60 } 61 } 62 } 63 64 void get_matric(ull a[N][N]) 65 { 66 for(int i=0; i<sz; i++) 67 { 68 if(ed[i]) continue; 69 for(int j=0; j<alp; j++) 70 { 71 if(ed[ch[i][j]]) continue; 72 a[i][ch[i][j]] ++; 73 } 74 } 75 for(int i=0; i<=sz; i++) a[i][sz] = 1; 76 } 77 78 ull quick_mod(ull a, ull b) 79 { 80 ull c = 1; 81 while(b) { 82 if(b&1) c = c * a; 83 a = a * a; 84 b >>= 1; 85 } 86 return c; 87 } 88 ///pow(a,1)+pow(a,2)+...+pow(a,x); 89 ull sum_pow(ull a, ull x) 90 { 91 if(x == 1) return a; 92 if(x&1) return quick_mod(a,x) + sum_pow(a, x-1); 93 return sum_pow(a, x>>1) * (quick_mod(a, x>>1)+1); 94 } 95 void mul(ull a[N][N], ull b[N][N]) 96 { 97 ull c[N][N] = {0}; 98 for(int k=0; k<=sz; k++) 99 for(int i=0; i<=sz; i++) 100 for(int j=0; j<=sz; j++) 101 c[i][j] += a[i][k]*b[k][j]; 102 memcpy(a, c, sizeof c); 103 } 104 void mat_pow(ull a[N][N], ull b) 105 { 106 ull c[N][N] = {0}; 107 for(int i=0; i<=sz; i++) 108 c[i][i] = 1; 109 while(b){ 110 if(b&1) mul(c, a); 111 mul(a, a); 112 b >>= 1; 113 } 114 memcpy(a, c, sizeof c); 115 } 116 117 char s[12]; 118 int main() 119 { 120 int i,j,k; 121 ull n, m; 122 while(scanf("%I64u %I64u",&n,&m) == 2) 123 { 124 init(); 125 for(i=1; i<=n; i++) scanf("%s",s), Insert(s); 126 build(); 127 ull a[N][N] = {0}; 128 get_matric(a); 129 mat_pow(a, m); 130 ull ans = 0; 131 for(i=0; i<=sz; i++) ans += a[0][i]; 132 printf("%I64u ",sum_pow(26,m)-ans+1); 133 } 134 return 0; 135 }
6.poj 1625 Censored!
给你一个不超过50的字典,然后还有不超过10个长度不超过50的非法串,问有多少个由字典组成的长度为m的串?没有取模。
我真想喷这题,没有任何思维上的新点,就是恶心+恶心。
1).卡内存,明显可以按poj 2778的套路,构造500*500矩阵搞就行了,但是,卡内存啊。注意到m不大,那就dp吧,dp[i][j]表示串长为i时在自动机j节点上的合法串的数量,状态转移也很简单:遍历字母表,如果j节点接收某个字符后转移到k节点,那么dp[i][k] += dp[i-1][j],初始dp[0][0] = 1。
2).ASCALL可高达255,我用java写的更坑爹,由于java中char是2个字节,有时会把输入的两个字符当成一个字符输入。。好吧,又改成了byte型的才行。
3).那么到底输入的p个串是由字母表构成的吗?我怎么测出来非法串还有其他字符啊,按照大家的做法,把不在字典中的字符哈希为0就稀里糊涂过了。
真的是够了,出题人的良心呢?

1 import java.math.BigInteger; 2 import java.util.Arrays; 3 import java.util.LinkedList; 4 import java.util.Queue; 5 import java.util.Scanner; 6 7 public class Main{ 8 static int alp; 9 static final int maxn = 505; 10 static int ch[][] = new int[maxn][55]; 11 static int ed[] = new int[maxn]; 12 static int fail[] = new int[maxn]; 13 static int Hash[] = new int[66666]; 14 static BigInteger dp[][] = new BigInteger[55][maxn]; 15 static int root, sz; 16 static Queue<Integer> q = new LinkedList<Integer>(); 17 18 static int newnode() { 19 Arrays.fill(ch[sz], -1); 20 ed[sz++] = 0; 21 return sz-1; 22 } 23 24 static void init() 25 { 26 sz = 0; 27 root = newnode(); 28 } 29 30 static void Insert(byte str[]){ 31 int now = root; 32 for(int i=0; i<str.length; i++) { 33 int t = str[i]; 34 t += 128; 35 if(ch[now][Hash[t]] == -1) 36 ch[now][Hash[t]] = newnode(); 37 now = ch[now][Hash[t]]; 38 } 39 ed[now] = 1; 40 } 41 42 static void build() 43 { 44 q.clear(); 45 fail[root] = root; 46 for(int i=0; i<alp; i++){ 47 if(ch[root][i] == -1) ch[root][i] = root; 48 else { 49 fail[ch[root][i]] = root; q.add(ch[root][i]); 50 } 51 } 52 while(!q.isEmpty()) { 53 int now = q.remove(); 54 for(int i=0; i<alp; i++) 55 { 56 if(ch[now][i] == -1) ch[now][i] = ch[fail[now]][i]; 57 else { 58 fail[ch[now][i]] = ch[fail[now]][i]; 59 ed[ch[now][i]] |= ed[fail[ch[now][i]]]; 60 q.add(ch[now][i]); 61 } 62 } 63 } 64 } 65 static void memset(BigInteger a[][], BigInteger x) 66 { 67 for(int i=0; i<a.length; i++) 68 for(int j=0; j<a[i].length; j++) 69 a[i][j] = x; 70 } 71 public static void main(String[] args) { 72 @SuppressWarnings("resource") 73 Scanner in = new Scanner(System.in); 74 int n, m, p; 75 while(in.hasNext()) 76 { 77 n = in.nextInt(); m = in.nextInt(); p = in.nextInt(); 78 Arrays.fill(Hash, 0); 79 byte s[] = in.next().getBytes(); 80 for(int i=0; i<n; i++) { 81 int t = s[i]; 82 t += 128; 83 Hash[t] = i; 84 } 85 init(); 86 alp = n; 87 for(int i=1; i<=p; i++) 88 { 89 byte str[] = in.next().getBytes(); 90 Insert(str); 91 } 92 build(); 93 memset(dp, BigInteger.ZERO); 94 dp[0][0] = BigInteger.ONE; 95 for(int i=1; i<=m; i++) 96 for(int j=0; j<sz; j++) 97 { 98 if(ed[j] > 0) continue; 99 for(int k=0; k<n; k++) 100 { 101 int tmp = ch[j][k]; 102 if(ed[tmp] > 0) continue; 103 dp[i][tmp] = dp[i][tmp].add(dp[i-1][j]); 104 } 105 } 106 BigInteger ans = BigInteger.ZERO; 107 for(int i=0; i<sz; i++) ans = ans.add(dp[m][i]); 108 System.out.println(ans); 109 } 110 } 111 }
7.hdu 2825 Wireless Password
给m个单词,问长为n的至少包含k个单词的串有多少个?(0<=m<=10, 1<=n<=25)
dp的状态和当前串长、Trie节点、已含单词集有关,那么dp[i][j][k]就表示当前串长为i,在Trie的j节点,状态为k(0<=k<2^10)。初始dp[0][0][0] = 1。
有两种转移方式:要么枚举dp[i][j][k]的前一个状态加到dp[i][j][k],要么枚举dp[i][j][k]的后一个状态,将dp[i][j][k]的值加到后一个状态。
复杂度均为O(25*100*2048*26),但是时限比较紧,我们选择后者方式,如果dp[i][j][k]=0,直接continue,它对后续状态无贡献。再者,数据数较多,不能每次用memset清空dp数组。这样优化后大概就能A掉了。

1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #include<queue> 7 using namespace std; 8 typedef unsigned long long ull; 9 const int N = 50; 10 const int mod = 20090717; 11 const int alp = 26; 12 const int maxn = 555; 13 int ch[maxn][alp], ed[maxn], fail[maxn]; 14 int root, sz; 15 16 int newnode() 17 { 18 memset(ch[sz], -1, sizeof(ch[sz])); 19 ed[sz++] = 0; 20 return sz-1; 21 } 22 void init() 23 { 24 sz = 0; 25 root = newnode(); 26 } 27 28 void Insert(char str[], int id) 29 { 30 int now = root; 31 for(int i=0; str[i]; i++) 32 { 33 int &tmp = ch[now][str[i]-'a']; 34 if(tmp == -1) tmp = newnode(); 35 now = tmp; 36 } 37 ed[now] |= 1<<id-1; 38 } 39 40 void build() 41 { 42 queue<int> q; 43 fail[root] = root; 44 for(int i=0; i<alp; i++) 45 { 46 int &tmp = ch[root][i]; 47 if(tmp == -1) tmp = root; 48 else fail[tmp] = root, q.push(tmp); 49 } 50 while(!q.empty()) 51 { 52 int now = q.front(); 53 q.pop(); 54 for(int i=0; i<alp; i++) 55 { 56 int &tmp = ch[now][i]; 57 if(tmp == -1) tmp = ch[fail[now]][i]; 58 else fail[tmp] = ch[fail[now]][i], 59 ed[tmp] |= ed[fail[tmp]], 60 q.push(tmp); 61 } 62 } 63 } 64 char str[25]; 65 int dp[26][110][1<<10]; 66 int num[1<<10]; 67 68 int solve(int n, int m, int k) 69 { 70 for(int i=0; i<=n; i++) 71 for(int j=0; j<sz; j++) 72 for(int sta=0; sta<(1<<m); sta++) 73 dp[i][j][sta] = 0; 74 dp[0][0][0] = 1; 75 /***************************************** 76 for(int len=1; len<=n; len++) 77 for(int pre=0; pre<sz; pre++) 78 for(int sta=0; sta<(1<<m); sta++) 79 { 80 for(int a=0; a<alp; a++) 81 { 82 int tmp = ch[pre][a]; 83 if(ed[tmp] && !(sta&(1<<ed[tmp]-1))) continue; 84 int &p = dp[len][tmp][sta]; 85 if(ed[tmp]) 86 p = (p + dp[len-1][pre][sta]+dp[len-1][pre][sta^(1<<ed[tmp]-1)]) % mod; 87 else p = (p + dp[len-1][pre][sta])%mod; 88 if(p > 1000000000) p %= mod; 89 } 90 } 91 *****************************************************/ 92 for(int len=0; len<n; len++) 93 for(int pre=0; pre<sz; pre++) 94 for(int sta=0; sta<(1<<m); sta++) 95 { 96 if(dp[len][pre][sta] == 0) continue; 97 for(int a=0; a<alp; a++) 98 { 99 int tmp = ch[pre][a]; 100 int &pp = dp[len+1][tmp][sta|ed[tmp]]; 101 pp += dp[len][pre][sta]; 102 if(pp > 1000000000) pp %= mod; 103 } 104 } 105 int ans = 0; 106 for(int now=0; now<sz; now++) 107 for(int sta=0; sta<(1<<m); sta++) 108 if(num[sta] >= k) 109 { 110 ans += dp[n][now][sta]; 111 if(ans > 1000000000) ans %= mod; 112 } 113 return ans%mod; 114 } 115 int main() 116 { 117 int i,j,k,m,n; 118 memset(num, 0, sizeof(num)); 119 num[0] = 0, num[1] = 1; 120 for(int i=2; i<(1<<10); i++) 121 num[i] = num[i>>1] + (i&1); 122 while(scanf("%d %d %d",&n,&m,&k) == 3 && n+m+k) 123 { 124 init(); 125 for(i=1; i<=m; i++) 126 scanf("%s",str), Insert(str, i); 127 build(); 128 printf("%d ",solve(n,m,k)); 129 } 130 return 0; 131 }
8.hdu 2296 Ring
给m个单词和长为L的串,问至少需要改变几个字符使得L串不包含任意一个单词。单词50个*20,L<=1000,字母均由'A''C''G''T'组成。
令dp[i][j]表示子串s[0,i-1]止于状态j不包含单词所需要改变字符数,初始dp数组inf, dp[0][0] = 0。那么dp[i][j] = min{dp[i-1][k]+(s[i-1] != c) | k由字符c转移到j}。同样,也可以由dp[i][j]来更新dp[i+1][k],并且加入dp[i][j] != inf优化。

1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #include<queue> 7 using namespace std; 8 typedef unsigned long long ull; 9 const int inf = 0x3f3f3f3f; 10 const int N = 1111; 11 const int alp = 4; 12 const int maxn = 555; 13 int Hash['Z']; 14 char ALP[4] = {'A','C','G','T'}; 15 int ch[maxn][alp], ed[maxn], fail[maxn]; 16 int root, sz; 17 18 int newnode() 19 { 20 memset(ch[sz], -1, sizeof(ch[sz])); 21 ed[sz++] = 0; 22 return sz-1; 23 } 24 void init() 25 { 26 sz = 0; 27 root = newnode(); 28 } 29 30 void Insert(char str[]) 31 { 32 int now = root; 33 for(int i=0; str[i]; i++) 34 { 35 int &tmp = ch[now][Hash[str[i]]]; 36 if(tmp == -1) tmp = newnode(); 37 now = tmp; 38 } 39 ed[now] = 1; 40 } 41 42 void build() 43 { 44 queue<int> q; 45 fail[root] = root; 46 for(int i=0; i<alp; i++) 47 { 48 int &tmp = ch[root][i]; 49 if(tmp == -1) tmp = root; 50 else fail[tmp] = root, q.push(tmp); 51 } 52 while(!q.empty()) 53 { 54 int now = q.front(); 55 q.pop(); 56 for(int i=0; i<alp; i++) 57 { 58 int &tmp = ch[now][i]; 59 if(tmp == -1) tmp = ch[fail[now]][i]; 60 else fail[tmp] = ch[fail[now]][i], 61 ed[tmp] |= ed[fail[tmp]], 62 q.push(tmp); 63 } 64 } 65 } 66 char str[1100]; 67 int dp[N][maxn]; 68 69 void solve() 70 { 71 int len = strlen(str); 72 memset(dp, 0x3f, sizeof(dp)); 73 dp[0][0] = 0; 74 int now = root; 75 for(int len=0; str[len]; len++) 76 for(int pre=0; pre<sz; pre++) 77 { 78 if(ed[pre]) continue; 79 if(dp[len][pre] == inf) continue; 80 for(int a=0; a<alp; a++) 81 { 82 int tmp = ch[pre][a]; 83 if(ed[tmp]) continue; 84 dp[len+1][tmp] = min(dp[len+1][tmp], dp[len][pre]+(str[len] != ALP[a])); 85 } 86 } 87 int ans = inf; 88 for(int i=0; i<sz; i++) 89 if(!ed[i]) ans = min(ans, dp[len][i]); 90 if(ans == inf) puts("-1"); 91 else printf("%d ",ans); 92 } 93 int main() 94 { 95 int i,j,k,m,n,t; 96 int cas = 0; 97 Hash['A'] = 0, Hash['C'] = 1, Hash['G'] = 2, Hash['T'] = 3; 98 while(scanf("%d",&n) == 1 && n) 99 { 100 init(); 101 for(i=1; i<=n; i++) 102 scanf("%s",str), Insert(str); 103 build(); 104 scanf("%s",str); 105 printf("Case %d: ",++cas); 106 solve(); 107 } 108 return 0; 109 }
9.zoj 3228 Searching the String
给一个长串len<=10^5,给N个0/1属性的长度不超过6的字符串,N<=10^5。
输出每个串在长串中匹配的个数,属性为0/1分别表示可/不可重叠。
对所有小串建立AC自动机,每个节点保留0/1属性串匹配的个数,再记录上一次1串匹配到此处的时间,若匹配到此处找到一串,那么只有时间差大于等于串的长度才能+1并更新此时间。0串完全是模板,直接++即可。

1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #include<queue> 7 using namespace std; 8 typedef unsigned long long ull; 9 const int inf = 0x3f3f3f3f; 10 const int N = 100010; 11 const int alp = 26; 12 const int maxn = 666666; 13 int ch[maxn][alp], ed[maxn][2], fail[maxn]; 14 int Hash[N]; 15 int root, sz; 16 int tim[maxn], num[maxn][2], len[maxn]; 17 int newnode() 18 { 19 memset(ch[sz], -1, sizeof(ch[sz])); 20 tim[sz] = 0; 21 ed[sz][0] = ed[sz][1] = 0; 22 num[sz][0] = num[sz][1] = 0; 23 return sz++; 24 } 25 void init() 26 { 27 sz = 0; 28 root = newnode(); 29 } 30 31 void Insert(char str[], int tag, int id) 32 { 33 int now = root, i; 34 for(i=0; str[i]; i++) 35 { 36 int &tmp = ch[now][str[i]-'a']; 37 if(tmp == -1) tmp = newnode(); 38 now = tmp; 39 } 40 Hash[id] = now; 41 ed[now][tag] = 1; 42 len[now] = i; 43 } 44 45 void build() 46 { 47 queue<int> q; 48 fail[root] = root; 49 for(int i=0; i<alp; i++) 50 { 51 int &tmp = ch[root][i]; 52 if(tmp == -1) tmp = root; 53 else fail[tmp] = root, q.push(tmp); 54 } 55 while(!q.empty()) 56 { 57 int now = q.front(); 58 q.pop(); 59 for(int i=0; i<alp; i++) 60 { 61 int &tmp = ch[now][i]; 62 if(tmp == -1) tmp = ch[fail[now]][i]; 63 else fail[tmp] = ch[fail[now]][i], 64 q.push(tmp); 65 } 66 } 67 } 68 69 char str[maxn], s[11]; 70 int tag[N]; 71 void solve(int n) 72 { 73 int now = root; 74 for(int i=0; str[i]; i++) 75 { 76 int tmp = now = ch[now][str[i]-'a']; 77 while(tmp != root) 78 { 79 if(ed[tmp][0]) { 80 num[tmp][0]++; 81 } 82 if(ed[tmp][1] && i+1-tim[tmp] >= len[tmp]) { 83 num[tmp][1]++; 84 tim[tmp] = i+1; 85 } 86 tmp = fail[tmp]; 87 } 88 } 89 for(int i=1; i<=n; i++) 90 printf("%d ",num[Hash[i]][tag[i]]); 91 puts(""); 92 } 93 94 int main() 95 { 96 int i,j,k,m,n,t,cas=0; 97 while(scanf("%s",str) == 1) 98 { 99 scanf("%d",&n); 100 init(); 101 for(i=1; i<=n; i++) 102 { 103 scanf("%d %s",&m, s); 104 tag[i] = m; 105 Insert(s, m, i); 106 } 107 build(); 108 printf("Case %d ",++cas); 109 solve(n); 110 } 111 return 0; 112 }
10.hdu 3341 RLost's revenge
给N个Len<=10的单词,再给一个长不超过40的串,将此串字母位置重置,使得包含最多的单词,N<=50。所有单词仅由字母'A''C''G''T'组成。
我们不用考虑输入长串的形态,只记录各个字母的个数就行了。dp很好想,令dp[i][j]表示在自动机i节点,字母的使用情况为j时包含的最多的单词数量。
难点就在于:如何表示'A''C''G''T'的使用情况?
假设有num[0]个'A',num[1]个'C',num[2]个'G',num[3]个'T',那么使用了a个'A',c个'C',g个'G',t个'T'可表示为a*(num[1]+1)*(num[2]+1)*(num[3]+1) + c*(num[2]+1)*num[3]+1) + g*(num[3]+1) + t。字母数均分时最大且<11*11*11*11。

1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #include<queue> 7 using namespace std; 8 typedef unsigned long long ull; 9 const int inf = 0x3f3f3f3f; 10 const int N = 11*11*11*11+5; 11 const int alp = 4; 12 const int maxn = 555; 13 int ch[maxn][alp], ed[maxn], fail[maxn]; 14 int Hash['Z']; 15 int root, sz; 16 int newnode() 17 { 18 memset(ch[sz], -1, sizeof(ch[sz])); 19 ed[sz] = 0; 20 return sz++; 21 } 22 void init() 23 { 24 sz = 0; 25 root = newnode(); 26 } 27 28 void Insert(char str[]) 29 { 30 int now = root, i; 31 for(i=0; str[i]; i++) 32 { 33 int &tmp = ch[now][Hash[str[i]]]; 34 if(tmp == -1) tmp = newnode(); 35 now = tmp; 36 } 37 ed[now] ++; 38 } 39 40 void build() 41 { 42 queue<int> q; 43 fail[root] = root; 44 for(int i=0; i<alp; i++) 45 { 46 int &tmp = ch[root][i]; 47 if(tmp == -1) tmp = root; 48 else fail[tmp] = root, q.push(tmp); 49 } 50 while(!q.empty()) 51 { 52 int now = q.front(); 53 q.pop(); 54 ed[now] += ed[fail[now]]; 55 for(int i=0; i<alp; i++) 56 { 57 int &tmp = ch[now][i]; 58 if(tmp == -1) tmp = ch[fail[now]][i]; 59 else fail[tmp] = ch[fail[now]][i], 60 q.push(tmp); 61 } 62 } 63 } 64 int dp[maxn][N]; 65 char str[44]; 66 int num[4]; 67 void Max(int &a, int b) {a = max(a, b);} 68 int main() 69 { 70 int i,j,k,m,n,t,cas=0; 71 Hash['A'] = 0, Hash['C']= 1, Hash['G'] = 2, Hash['T'] = 3; 72 while(scanf("%d",&n) == 1 && n) 73 { 74 init(); 75 memset(num, 0, sizeof(num)); 76 for(int i=1; i<=n; i++) 77 scanf("%s",str), Insert(str); 78 build(); 79 scanf("%s",str); 80 for(i=0; str[i]; i++) num[Hash[str[i]]]++; 81 memset(dp, 0, sizeof(dp)); 82 int mod3 = num[3]+1; 83 int mod2 = mod3 * (num[2]+1); 84 int mod1 = mod2 * (num[1]+1); 85 int limit = num[0]*mod1+num[1]*mod2+num[2]*mod3+num[3]; 86 memset(dp, -1, sizeof(dp)); 87 dp[0][0] = 0; 88 for(int a=0; a<=num[0]; a++) 89 for(int b=0; b<=num[1]; b++) 90 for(int c=0; c<=num[2]; c++) 91 for(int d=0; d<=num[3]; d++) 92 { 93 int tp = a*mod1 + b*mod2 + c*mod3 + d; 94 for(int i=0; i<sz; i++) 95 { 96 if(dp[i][tp] == -1) continue; 97 if(a < num[0]) Max(dp[ch[i][0]][tp+mod1], dp[i][tp]+ed[ch[i][0]]); 98 if(b < num[1]) Max(dp[ch[i][1]][tp+mod2], dp[i][tp]+ed[ch[i][1]]); 99 if(c < num[2]) Max(dp[ch[i][2]][tp+mod3], dp[i][tp]+ed[ch[i][2]]); 100 if(d < num[3]) Max(dp[ch[i][3]][tp+1], dp[i][tp]+ed[ch[i][3]]); 101 } 102 } 103 int ans = 0; 104 for(int i=0; i<sz; i++) 105 for(int j=0; j<=limit; j++) 106 Max(ans, dp[i][j]); 107 printf("Case %d: %d ",++cas, ans); 108 109 } 110 return 0; 111 }
11. hdu 3247 Resource Archiver
给n个资源串和m病毒串,现要把资源串串起来,两串可以重叠,且不含病毒串,求串起来的最短长度。2 <= n <= 10, 1 <= m <= 1000,资源串长不超过10^5,病毒串总长不超过5w。所有串仅由'0''1'组成。
很直接的DP方式就是用dp[i][j]表示在Trie树i节点时,匹配情况是j的最短长度。但是6w*(1<<10)MLE。。。
翻了翻网上的题解,大都是说让j表示在第j个单词的末尾。加单词间的最短路。仔细想一想,不对啊,有的资源串的后缀包含了另一个资源,那么另一个资源串不就有多个结尾了吗?
还有一种解释是说把带有标记的节点拿出来,最短路+dp,尼玛,最坏情况下每个节点都有标记啊。
我看了看他们的代码,发现都一样,那就一个结论了:所有资源串都不是另一个资源串的前缀或后缀。好吧,那就直接用单词末尾的节点来dp吧,先BFS处理任意两个单词末节点的最短路,它的意义是第二个串长与第一个串后缀重合部分长的差,BFS不经过病毒节点就行了。

1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #include<queue> 7 using namespace std; 8 typedef unsigned long long ull; 9 const int inf = 0x3f3f3f3f; 10 const int N = 55555; 11 const int alp = 2; 12 const int maxn = 60066; 13 int ch[maxn][alp], ed[2][maxn], fail[maxn]; 14 int ID[11]; ///第i个串末在自动机中的编号 15 int root, sz; 16 int newnode() 17 { 18 memset(ch[sz], -1, sizeof(ch[sz])); 19 ed[0][sz] = ed[1][sz] = 0; 20 return sz++; 21 } 22 void init() 23 { 24 sz = 0; 25 root = newnode(); 26 } 27 28 void Insert(char str[], int id, int tag) 29 { 30 int now = root, i; 31 for(i=0; str[i]; i++) 32 { 33 int &tmp = ch[now][str[i]-'0']; 34 if(tmp == -1) tmp = newnode(); 35 now = tmp; 36 } 37 ed[id][now] |= (1<<tag-1); 38 if(!id) ID[tag] = now; 39 } 40 41 void build() 42 { 43 queue<int> q; 44 fail[root] = root; 45 for(int i=0; i<alp; i++) 46 { 47 int &tmp = ch[root][i]; 48 if(tmp == -1) tmp = root; 49 else fail[tmp] = root, q.push(tmp); 50 } 51 while(!q.empty()) 52 { 53 int now = q.front(); 54 q.pop(); 55 ed[0][now] |= ed[0][fail[now]]; 56 ed[1][now] |= ed[1][fail[now]]; 57 for(int i=0; i<alp; i++) 58 { 59 int &tmp = ch[now][i]; 60 if(tmp == -1) tmp = ch[fail[now]][i]; 61 else fail[tmp] = ch[fail[now]][i], 62 q.push(tmp); 63 } 64 } 65 } 66 int dis[maxn]; 67 bool vis[maxn]; 68 int w[11][11]; 69 void get_dis(int s, int n) 70 { 71 memset(dis, 0x3f, sizeof(dis)); 72 memset(vis, 0, sizeof(vis)); 73 dis[ID[s]] = 0; 74 vis[ID[s]] = 1; 75 queue<int>q; 76 q.push(ID[s]); 77 while(!q.empty()) { 78 int u = q.front(); q.pop(); 79 for(int i=0; i<alp; i++) 80 { 81 int tmp = ch[u][i]; 82 if(ed[1][tmp]) continue; 83 if(vis[tmp]) continue; 84 vis[tmp] = 1; 85 dis[tmp] = dis[u] + 1; 86 q.push(tmp); 87 } 88 } 89 for(int i=1; i<=n; i++) 90 w[s][i] = dis[ID[i]]; 91 } 92 char str[N]; 93 int len[11]; 94 int dp[1<<10][11]; 95 int main() 96 { 97 int i,j,k,m,n; 98 int p[11]; 99 while(scanf("%d %d",&n,&m) == 2 && n+m) 100 { 101 init(); 102 int sum = 0; 103 for(i=1; i<=n; i++) 104 scanf("%s", str), len[i] = strlen(str), Insert(str, 0, i); 105 for(i=1; i<=m; i++) 106 scanf("%s", str), Insert(str, 1, i); 107 build(); 108 ID[0] = root; 109 memset(w, 0x3f, sizeof(w)); 110 for(i=0; i<=n; i++) get_dis(i,n); 111 int ans = inf; 112 /*********************************** 113 *枚举顺序,简单粗暴 114 for(i=1; i<=n; i++) p[i] = i; 115 do { 116 int ret = 0; 117 for(j=1; j<n; j++) 118 ret += w[p[j]][p[j+1]]; 119 ans = min(ans, ret+len[p[1]]); 120 } while(next_permutation(p+1, p+1+n)); 121 ************************************/ 122 memset(dp, 0x3f, sizeof(dp)); 123 dp[0][0] = 0; 124 for(i=0; i<(1<<n); i++) 125 for(j=0; j<=n; j++) 126 { 127 if(dp[i][j] == inf) continue; 128 for(k=1; k<=n; k++) 129 { 130 if(w[j][k] == inf) continue; 131 dp[i|(1<<k-1)][k] = min(dp[i|(1<<k-1)][k], dp[i][j] + w[j][k]); 132 } 133 } 134 for(i=1; i<=n; i++) ans = min(ans, dp[(1<<n)-1][i]); 135 printf("%d ",ans); 136 } 137 return 0; 138 }
12. zoj 3494 Detect the Viru
给n个01病毒串,然后问区间[L,R]内有多少个数的BCD encoding不包含病毒串。一个数的BCD encoding是指每一位数的4bit位连起来的串。
n<=100,病毒串长小于20,L<=R<10^200。
很好想的数位DP+AC自动机dp[i][j]表示在还有i位且当前在Trie数的j节点的合法数字的个数。
注意,如果用字符串读入L和R,要特判L。

1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #include<queue> 7 using namespace std; 8 typedef unsigned long long ull; 9 const int inf = 0x3f3f3f3f; 10 const int mod = 1000000009; 11 const int N = 222; 12 const int alp = 10; 13 const int maxn = 2222; 14 int ch[maxn][alp], ed[maxn], fail[maxn]; 15 int root, sz; 16 int newnode() 17 { 18 memset(ch[sz], -1, sizeof(ch[sz])); 19 ed[sz] = 0; 20 return sz++; 21 } 22 void init() 23 { 24 sz = 0; 25 root = newnode(); 26 } 27 28 void Insert(char str[]) 29 { 30 int now = root, i; 31 for(i=0; str[i]; i++) 32 { 33 int &tmp = ch[now][str[i]-'0']; 34 if(tmp == -1) tmp = newnode(); 35 now = tmp; 36 } 37 ed[now] = 1; 38 } 39 40 void build() 41 { 42 queue<int> q; 43 fail[root] = root; 44 for(int i=0; i<alp; i++) 45 { 46 int &tmp = ch[root][i]; 47 if(tmp == -1) tmp = root; 48 else fail[tmp] = root, q.push(tmp); 49 } 50 while(!q.empty()) 51 { 52 int now = q.front(); 53 q.pop(); 54 ed[now] |= ed[fail[now]]; 55 for(int i=0; i<alp; i++) 56 { 57 int &tmp = ch[now][i]; 58 if(tmp == -1) tmp = ch[fail[now]][i]; 59 else fail[tmp] = ch[fail[now]][i], 60 q.push(tmp); 61 } 62 } 63 } 64 65 int sel[10][4] = {0,0,0,0, 0,0,0,1, 0,0,1,0, 0,0,1,1, 0,1,0,0, 66 0,1,0,1, 0,1,1,0, 0,1,1,1, 1,0,0,0, 1,0,0,1}; 67 int query(char str[]) 68 { 69 int len = strlen(str+1); 70 int now = root; 71 for(int i=1; i<=len; i++) 72 { 73 int a = str[i] - '0'; 74 for(int j=0; j<4; j++) 75 { 76 int tmp = now = ch[now][sel[a][j]]; 77 while(tmp) { 78 if(ed[tmp]) return 0; 79 tmp = fail[tmp]; 80 } 81 } 82 } 83 return 1; 84 } 85 char s[N]; 86 int dp[N][maxn][2]; 87 int DFS(int pos, int id, bool limit, bool pre) 88 { 89 if(!pos) return pre; 90 if(!limit && dp[pos][id][pre] != -1) return dp[pos][id][pre]; 91 int ret = 0; 92 int end = limit ? s[pos]-'0' : 9; 93 for(int i=0; i<=end; i++) 94 { 95 bool ok = true; 96 int tmp = id; 97 if(pre || i) 98 for(int j=0; j<4; j++) 99 { 100 tmp = ch[tmp][sel[i][j]]; 101 if(ed[tmp]) ok = false; 102 } 103 if(ok) ret =(ret+DFS(pos-1, tmp, limit&&i==end, pre || i))%mod; 104 } 105 if(!limit) dp[pos][id][pre] = ret; 106 return ret; 107 } 108 int main() 109 { 110 int i,j,k,m,n,t; 111 scanf("%d",&t); 112 while(t--) 113 { 114 init(); 115 scanf("%d",&n); 116 while(n--) scanf("%s",s+1), Insert(s+1); 117 build(); 118 scanf("%s",s+1); 119 m = strlen(s+1); 120 int ans = -query(s); 121 reverse(s+1, s+1+m); 122 for(i=0; i<=m; i++) for(j=0; j<sz; j++) dp[i][j][0]=dp[i][j][1]=-1; 123 ans += DFS(m, 0, 1, 0); 124 scanf("%s",s+1); 125 m = strlen(s+1); 126 reverse(s+1, s+1+m); 127 for(i=0; i<=m; i++) for(j=0; j<sz; j++) dp[i][j][0]=dp[i][j][1]=-1; 128 ans = (DFS(m, 0, 1, 0) - ans)%mod; 129 printf("%d ",(ans+mod)%mod); 130 } 131 return 0; 132 }
入门题暂时做到这里了( ^_^ )/~~。