4502: 串
Time Limit: 30 Sec Memory Limit: 512 MBSubmit: 245 Solved: 123
[Submit][Status][Discuss]
Description
兔子们在玩字符串的游戏。首先,它们拿出了一个字符串集合S,然后它们定义一个字
符串为“好”的,当且仅当它可以被分成非空的两段,其中每一段都是字符串集合S中某个字符串的前缀。
比如对于字符串集合{"abc","bca"},字符串"abb","abab"是“好”的("abb"="ab"+"b",abab="ab"+"ab"),而字符串“bc”不是“好”的。
兔子们想知道,一共有多少不同的“好”的字符串。
Input
第一行一个整数n,表示字符串集合中字符串的个数
接下来每行一个字符串
Output
一个整数,表示有多少不同的“好”的字符串
Sample Input
2
ab
ac
ab
ac
Sample Output
9
HINT
1<=n<=10000,每个字符串非空且长度不超过30,均为小写字母组成。
Source
【题解】
感觉稍微了解了一点AC自动机上DP的套路。。
先考虑答案串在AC自动机上匹配会怎样
1、没有失配,说明答案串是某个串的前缀,但是答案串应是两个非空串拼起来的,因此要判断匹配终点是否有fail值,有fail值才能加入答案
2、失配了,失配之后可能会跳很多fail,到达终点v
关键是第二种情况如何统计?
一种状态是dp[l][i]表示已匹配串长为l,失配过,到达点i的方案数。转移时枚举下一个字符,若没有需要跳fail。
不跳fail可以直接转移,但是如果跳fail,怎么保证跳fail跳到终点v后,root->v能跟前面的串拼接或有重叠部分拼接呢?
画画图,发现root->v的串长必须大于等于第一次失配后又添加字符的串长,不然肯定形成的串中间有一部分既不属于第一次失配前的串也不属于root->v
显然dp[l][i]是不够的应有dp[l][i][j]表示已匹配串长为l,到达点i,第一次在j点失配
进一步观察发现,如果两个字符串第一次失配的位置一样,最终到达的位置也一样,这两个串相同
因此每一个串与失配的地方和最终到达的地方是一一对应的
为了方便转移,我们选最终到达的地方,统计由多少失配的地方走过来,为了判断答案是否符合要求,还应记录第一次失配后又添加串长是多少
于是就有dp[i][j]表示第一次失配后又走了i步,到达j的方案
枚举下一个字母k,j儿子里有k直接转移,没有就按匹配的规则,跳到有k的fail转移
这里加了一点优化,就是插入的时候,如果没有ch[u][k],就让他连上ch[fail[u][k],这样就省去了跳的一步,获得更加稳定的复杂度
因此要加一个vis[u][k]表示u原本有没有k这个儿子
别忘了第一种情况
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <cstdlib> 5 #include <algorithm> 6 #define max(a, b) ((a) > (b) ?(a) : (b)) 7 inline void read(long long &x) 8 { 9 x = 0;char ch = getchar(), c = ch; 10 while(ch < '0' || ch > '9') c = ch, ch = getchar(); 11 while(ch <= '9' && ch >= '0') x = x * 10 + ch - '0', ch = getchar(); 12 if(c == '-') x = -x; 13 } 14 15 const long long INF = 0x3f3f3f3f; 16 const long long MAXN = 10000 + 5; 17 const long long MAXLEN = 30 + 5; 18 const long long MAXNODE = MAXN * MAXLEN + 10; 19 20 long long n,cnt,ch[MAXNODE][30], vis[MAXNODE][30], deep[MAXNODE], tag[MAXNODE], fail[MAXNODE], refail[MAXNODE]; 21 char s[MAXN][MAXLEN]; 22 23 void insert(long long x) 24 { 25 long long now = 0; 26 for(long long i = 1;s[x][i] != '