标签:
题意:给定两个长度不超过a,b(1 <= |a|,|b| <= 90000),x为a的连续子串,b为y的连续子串(x和y均可以是空串);问x+y形成的不同串的个数?
误区:开始一门心思想着求出总的可形成的字符串个数,再减去a,b中相同的子串重叠的部分;想通过连续插入a+b得到的SAM并不能获得信息;因为x,y是任意的子串,连续插入导致一定是a的后缀和b的前缀
正解:直接在计算有不同子串时,就不去计算重复的 <=>对于一个可能出现x后缀和y前缀相同而重复计算的情况,就全部加入x而y变为去掉前缀的部分;
即找到x最后一个字符ch,使得在x后能加的字符串不以ch开头,这样就避免了重复的情况;(思想很好啊!!!)
对SAM的理解:
SAM将字符串插入之后,step[np] - step[pre[np]]表示 状态np比父节点pre[np]多出的以相同的点为后缀的不同子串的个数;
对SAM进行拓扑之后:
<1>如果按照字符串先遍历有效节点,初始化每个状态出现的次数为1,就可从孩子节点得到父节点的出现的次数;再使用step得到不同子串的数目即可求解一类与出现次数有关的问题;如 hdu 4641 K-string
<2>如果初始化root为1,递推出每个节点出现的次数 cnt[ g[p][v] ] += cnt[p]将得到的是从初始状态到当前状态的所有子串(当然是不同的)数;
本题:需要对a字符串构成的SAM的每一个状态(节点)找出g[u][j] == 0(即上面讲的x不可能有j这条状态转移边),这时如果y以j开头的不同子串的数量已知,那就可以直接往结果中贡献出 cnt[u] * C[j]的值(cnt[u]表示a字符串以x状态为后缀不同子串的个数,C[j]表示y中以j开头子串的数目)
cnt集合根据上面的性质2可以直接递推得到,那C集合呢?
其实就是在找到一个cnt[u]之后,如果u有一条j的转移边j,那么C[j]的值就增加了cnt[u]?
还有一个问题:SAM只能求得以某个点为后缀的,怎么得到从每个点开始的呢?
那就将b逆序插入。。(好思想啊)这样后缀就变成了后缀里面的前缀
对于b的各种情况已经计算完了,但是由于可以有空子串,a的不同子串还需加上;
坑:这道题数据很大,怀疑是% unsigned long long判的结果,使用long long 直接WA了
注:如果C集合写在SAM里面,在调用SA时运算的SA的C集合。。不是SB的
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 typedef unsigned long long ll; 6 7 const int maxn = 100007; 8 const int SIGMA_SIZE = 26; 9 10 struct SAM{ 11 int sz, last, pos[maxn<<1]; 12 int g[maxn<<1][SIGMA_SIZE], pre[maxn<<1], step[maxn<<1]; 13 ll cnt[maxn<<1]; 14 15 void init(){ 16 sz = 0;last = 1; 17 newNode(0); 18 } 19 20 void newNode(int s){ 21 pre[++sz] = 0; 22 step[sz] = s; 23 memset(g[sz],0,sizeof(g[sz])); 24 } 25 26 int idx(char ch){ return ch - ‘a‘; } 27 28 void Insert(char ch); 29 void topoSort(); 30 void preSolve(ll* C); 31 ll solve(ll* C); 32 33 }SA, SB; 34 35 void SAM::Insert(char ch){ 36 newNode(step[last] + 1); 37 int v = idx(ch), np = sz, p = last; 38 while(p && !g[p][v]){ 39 g[p][v] = np; 40 p = pre[p]; 41 } 42 43 if(p){ 44 int q = g[p][v]; 45 if(step[q] == step[p] + 1) 46 pre[np] = q; 47 else{ 48 newNode(step[p] + 1); 49 int nq = sz; 50 for(int i = 0;i < SIGMA_SIZE;i++) 51 g[nq][i] = g[q][i]; 52 53 pre[nq] = pre[q]; 54 pre[q] = pre[np] = nq; 55 56 while(p && g[p][v] == q) 57 g[p][v] = nq, p = pre[p]; 58 } 59 } 60 else pre[np] = 1; 61 last = np; 62 } 63 64 void SAM::topoSort(){ 65 for(int i = 0; i <= sz; i++) cnt[i] = 0; 66 for(int i = 1; i <= sz; i++) cnt[step[i]]++; 67 for(int i = 1; i <= sz; i++) cnt[i] += cnt[i-1]; 68 for(int i = 1; i <= sz; i++) pos[cnt[step[i]]--] = i; 69 } 70 71 void SAM::preSolve(ll* C){ 72 topoSort(); 73 for(int i = 0;i <= sz; i++) cnt[i] = 0; 74 cnt[1] = 1; 75 76 for(int i = 1;i <= sz; i++){ //从root递推到每个节点出现的次数 77 int u = pos[i]; 78 for(int j = 0; j < SIGMA_SIZE; j++){ 79 int v = g[u][j]; 80 if(v == 0) continue; 81 C[j] += cnt[u]; 82 cnt[v] += cnt[u]; 83 } 84 } 85 } 86 87 ll SAM::solve(ll *C){ 88 topoSort(); 89 for(int i = 0; i <= sz; i++) cnt[i] = 0; 90 cnt[1] = 1; 91 92 ll ret = 0; 93 for(int i = 1; i <= sz; i++){ 94 int u = pos[i]; 95 for(int j = 0; j < SIGMA_SIZE; j++){ 96 int v = g[u][j]; 97 if(v == 0) ret += cnt[u] * C[j]; 98 else cnt[v] += cnt[u]; 99 } 100 ret += cnt[u]; // x + "" 101 } 102 return ret; 103 } 104 105 char str[maxn]; 106 int main() 107 { 108 int T; 109 scanf("%d",&T); 110 while(T--){ 111 scanf("%s", str); 112 int n = strlen(str); 113 SA.init(); 114 for(int i = 0;i < n; i++) 115 SA.Insert(str[i]); 116 117 scanf("%s", str); 118 n = strlen(str); 119 SB.init(); 120 for(int i = n - 1; i >= 0; i--) // ** 121 SB.Insert(str[i]); 122 123 ll C[SIGMA_SIZE]; 124 memset(C,0,sizeof(C)); 125 SB.preSolve(C); 126 127 printf("%llu\n",SA.solve(C)); 128 } 129 }
hdu 5343 MZL's Circle Zhou SAM
标签:
原文地址:http://www.cnblogs.com/hxer/p/5679721.html