题目大意:给定一些字符,将这些字符组成一个固定长度的字符串,但是字符串不能包含一些禁词,问你有多少种组合方式。
这是一道好题,既然出现了“一些”禁词,那么这题肯定和AC自动机有点关系了,对于这一题来说,因为我们需要的是求出在N^M种状态除去包含禁词的状态数,枚举肯定是不现实的了,我们唯一能做的只能是DP,但是怎么DP呢?只能是通过AC自动机来想了,由此我们来看一下trie树,我们知道trie树是可以表示不同字符串前后缀的转移关系的,但是这一题并不是要我们求出字串中是否有禁词,而是要我们求出除了禁词的其他组合方式,那么我们可不可以通过trie树直接看出来呢?答案是可以的,但是我们要改进一下。
如果我们把trie树中的Next数组补齐,比如在ACGT中含有禁词ACC,C,我们可以构建如下图的一个trie树
我们可以看到这棵trie树补齐了一般trie树不存在的边,其实就是把不存在的对应k子节点指向当前节点的fail节点的k节点,这样我们就可以找到所有元素转移的状态关系了,得到了这个东西,那么我们就可以用DP来把状态转移全部搞出来了,组成一个m长度的单词,只要我们不从失败状态(禁词的结尾)转出或者转入就OK了。
这样一开DP就很容易理解了,状态转移方程dp[i+1][转移状态]=dp[i][trie树上某个节点]+dp[i+1][转移状态](转移状态是指的是其他节点转移过来的情况),那么怎么标定非法情况呢?我们不仅要把单词结尾标记为非法状态,如果当前位置fail指针的指向的k位置也为单词结尾,那么当前位置的指向的k位置也必须标记为非法状态,因为我们不能从这个节点转出(也就意味着这个单词是包含着另一个单词的)。
1 #include <iostream> 2 #include <algorithm> 3 #include <functional> 4 #define MAX 130 5 6 using namespace std; 7 8 static int sum_node, Hash_Table[MAX]; 9 static char str[200]; 10 11 struct node 12 { 13 int if_end,num;//注意end位置不仅是标记单词的结束,而且还表示了是否能状态转移 14 node *fail, *Next[MAX]; 15 }Mem_Pool[MAX], *Trie_Node[MAX], *Queue[MAX * 2]; 16 struct BigInterget 17 { 18 int A[25]; 19 enum { MOD = 10000 }; 20 BigInterget()//构析函数用于初始化大数,A[0]表示大数的长度 21 { 22 memset(A, 0, sizeof(A)); 23 A[0] = 1; 24 } 25 void set(int x)//用于设定一个32位的整数 26 { 27 memset(A, 0, sizeof(A)); 28 A[0] = 1; A[1] = x; 29 } 30 void print(void) 31 { 32 printf("%d", A[A[0]]); 33 for (int i = A[0] - 1; i > 0; i--) 34 { 35 if (A[i] == 0){ printf("0000"); continue; } 36 for (int k = 10; k*A[i] < MOD; k *= 10) 37 printf("0"); 38 printf("%d", A[i]); 39 } 40 printf("\n"); 41 } 42 int& operator [] (int pos) { return A[pos]; } 43 const int& operator [] (int pos) const { return A[pos]; } 44 45 BigInterget operator + (const BigInterget &B) 46 { 47 BigInterget C; 48 C[0] = max(A[0], B[0]); 49 for (int i = 1; i <= C[0]; i++) 50 C[i] += A[i] + B[i], C[i + 1] += C[i] / MOD, C[i] %= MOD; 51 if (C[C[0] + 1] > 0)C[0]++;//进位 52 return C; 53 } 54 }; 55 static BigInterget dp[52][MAX]; 56 57 struct node *create_new_node(void); 58 void put_letter_to_hash(const int); 59 void Insert(struct node *); 60 void build_ac_automation(struct node *); 61 62 int main(void) 63 { 64 int Word_Length, Forbidden_Word_Sum, Letter_Type_Sum; 65 while (~scanf("%d%d%d", &Letter_Type_Sum, &Word_Length, &Forbidden_Word_Sum)) 66 { 67 sum_node = 0; 68 memset(Hash_Table, 0, sizeof(Hash_Table)); 69 node *root = create_new_node(); 70 put_letter_to_hash(Letter_Type_Sum); 71 72 for (int i = 0; i < Forbidden_Word_Sum; i++) 73 Insert(root); 74 build_ac_automation(root); 75 76 for (int i = 0; i <= Word_Length; i++) 77 for (int j = 0; j < sum_node; j++) 78 dp[i][j] = BigInterget(); 79 dp[0][0].set(1); 80 81 for (int i = 0; i < Word_Length; i++) 82 for (int j = 0; j < sum_node; j++) 83 { 84 if (Mem_Pool[j].if_end)//不能从非法状态中转入 85 continue; 86 for (int k = 0; k < Letter_Type_Sum; k++) 87 { 88 if (Mem_Pool[j].Next[k]->if_end)//不能从非法状态中转出 89 continue; 90 int id = Mem_Pool[j].Next[k]->num; 91 dp[i + 1][id] = dp[i][j] + dp[i + 1][id]; 92 } 93 } 94 BigInterget ans = BigInterget(); 95 for (int i = 0; i < sum_node; i++) 96 ans = ans + dp[Word_Length][i]; 97 ans.print(); 98 } 99 return EXIT_SUCCESS; 100 } 101 102 struct node *create_new_node(void) 103 { 104 node *tmp = &Mem_Pool[sum_node]; 105 tmp->fail = NULL; 106 tmp->if_end = 0; 107 tmp->num = sum_node++; 108 memset(tmp->Next, NULL, sizeof(struct node*)*MAX); 109 return tmp; 110 } 111 112 void put_letter_to_hash(const int Letter_Type_Sum) 113 { 114 getchar();//去掉回车换行 115 gets(str); 116 117 for (int i = 0; i < Letter_Type_Sum; i++) 118 Hash_Table[str[i]] = i; 119 } 120 121 void Insert(struct node *root) 122 { 123 gets(str); 124 struct node *tmp_ptr = root; 125 126 for (int i = 0; str[i] != '\0'; i++) 127 { 128 int id = Hash_Table[str[i]]; 129 if (tmp_ptr->Next[id] == NULL) 130 tmp_ptr->Next[id] = create_new_node(); 131 tmp_ptr = tmp_ptr->Next[id]; 132 } 133 tmp_ptr->if_end = 1; 134 } 135 136 void build_ac_automation(struct node *root) 137 { 138 int head = 0, tail = 0; 139 node *out = NULL; 140 root->fail = NULL; 141 Queue[tail++] = root; 142 143 while (head != tail) 144 { 145 out = Queue[head++]; 146 for (int i = 0; i < MAX; i++) 147 if (out->Next[i] != NULL) 148 { 149 if (out == root) 150 out->Next[i]->fail = root; 151 else 152 { 153 out->Next[i]->fail = out->fail->Next[i]; 154 //如果还找到在其他地方找到和他一样的元素,那么我们就把失败指针指向这个元素,同时要设定合法状态 155 out->Next[i]->if_end = out->fail->Next[i]->if_end ? 1 : out->Next[i]->if_end; 156 } 157 Queue[tail++] = out->Next[i]; 158 } 159 else 160 { 161 if (out == root) out->Next[i] = root; 162 else out->Next[i] = out->fail->Next[i]; 163 } 164 } 165 }
另外这个题要用到大数加法,在网上找了个很好的模板,以后就用这个了
参考:http://blog.csdn.net/AndyTeen/article/details/45668121