AC自动机实际上是将字符串集合与状态一一对应,也算是一种状态压缩的手段。
对于状态,一般考虑这么几个要素:
1.考虑到了主串的第几位,这个一般就是状态的要素之一(其中一维),空间不够时可以考虑滚动数组(i&1)
2.自动机上的状态。这里注意自动机上的某点可能对应多个字符串的最后一位,因此在设置失败指针时,如有需要,可以记录下各个节点分别是哪些字符串的最后一位(可以用链表,也可以按位压缩为int)。转移的时候只考虑next数组的指向(注意这里可以做一个优化,若字典树中某节点不存在next子节点,可令其next子节点直接为失败指针,这样就构成了Trie图)。
3.关于模式串。这个一般见于关于字符串构造的DP。可以记录数目,也可以按位压缩记录哪些串被包含。见题:ZOJ3545 Rescue the Rabbit
关于状态的设定,可以按着一种思维方式去考虑:从某位往后,需要知道哪些状态才能不考虑前面的情况?或者说之前的那些要素可以影响后面?
另外关于AC自动机,可以考虑静态开点(事实上要建Trie图必须静态开点)。具体见代码(AC自动机静态设置,结合以上三种状态的DP)
4.AC自动机是以Trie图的形式展现,每个点表示AC自动机上的状态。那么当DP时如果自动机上无意义的状态过多,可以考虑加入BFS最短路进行优化,只考虑有意义的点,这是对DP时间空间进行压缩的一种方法。见题:ZOJ3190。令dp[i][j]表示自动机上状态为i,构造出的串包含目标串的情况为j时的最小长度。由于当i不为目标串后缀时,j的值不会改变,也就是说答案必定来自i为自动机上i目标串后缀的情况,这种情况最多55种,复杂度就大大减小。DP之前求解出各个有意义状态之间不经过病毒串条件下的最短路长度。在此基础上进行DP即可。(DP+最短路优化来压缩状态点)。
关于ZOJ3545
代码:
#include<cstdio> #include<cstring> #include<queue> #include<map> using namespace std; #define INF (1<<25) #define max(x,y) ((x)<(y)?(y):(x)) struct Node{ int next[4],fail,sta; Node(){ for(int i=0;i<4;++i) next[i]=0;fail=0;sta=0; } }; Node node[1005]; bool dp[2][1005][1<<10]; map<char,int> ID; queue<int> q; int w[15],cnt,n,l; void init(){ for(int i=0;i<=cnt;++i){ for(int j=0;j<4;++j) node[i].next[j]=0; node