首页 > 4566: [Haoi2016]找相同字符 SAM

4566: [Haoi2016]找相同字符 SAM

折腾了好久。不过收获还是很多的。第一次自己去画SAM所建出来fail树。深入体会了这棵树的神奇性质。

当然,我最终靠着自己A掉了。(这是我第一次推SAM的性质(以前都是抄别人的,感觉自己好可耻),不过感觉好像是摸着黑行走啊!)

这道题,可以先对第一个串建出后缀自动机。然后第二个串在后缀自动机上跑。

首先,SAM所建出的fail树的性质有:

1: 树上一个节点对应了多个串,串的个数是 len[x] - len[fa[x]], 同时它们都出现了 sz[x] 次(感觉好像说不大清,可以看一下代码对于sz数组的处理)。

2: 设串的长度为 l 那么每个节点 x 的 sz[x] * (len[x] - len[fa[x]]) 的和为 (l+1) * l / 2

3:   树上一个节点的所有后代所代表的串都是以这个节点代表的串为后缀的串。

4:   根据性质3推出: 如果一个字符串匹配到一个树上的某个节点, 那么这个节点的所有祖先所代表的所有祖先都是它的子串,但那个节点本身所代表的所以串并不一定都是他的子串。

 

所以这道题的ans应该呼之欲出了。

(说了这么多,你们知道解题的思路是什么吗?显然对于子串的问题,我们肯定是假设当前匹配到第i个字符的时候,把以第i个字符结尾的所有字串都处理掉。) 

那么假设当前匹配到 i 字符时转移到了节点x, 那么x对答案的贡献就是 祖先所代表的所有的串(可以预处理)和  匹配到 i 字符时,(第二个串与第一个串的最长匹配长度-len[fa[x]] )*sz[x] 

第二个串与第一个串的最长匹配长度-len[fa[x]](这个就是第二个串在x节点内所能匹配的子串数)

 

 1 #include
 2 #include
 3 #include
 4 #define rep(i,j,k) for(register int i = j; i <= k; i++)
 5 #define dow(i,j,k) for(register int i = j; i >= k; i--)
 6 #define ez(i,j) for(edge*i = head[j]; i; i=i->next)
 7 #define maxn 200005
 8 #define ll long long
 9 using namespace std;
10 
11 struct edge{ int to; edge*next; } e[maxn<<1], *pt = e, *head[maxn<<1];
12 inline void add(int x,int y) { pt->to = y, pt->next = head[x], head[x] = pt++; }
13 
14 int last = 1, u, v, nv, p, tot = 1, sz[maxn<<1], dep[maxn<<1], fa[maxn<<1], son[maxn<<1][26];
15 inline void extend(int c) {
16     u = last; p = last = ++tot; dep[p] = dep[u] + 1; sz[p] = 1;
17     while( u && !son[u][c] ) son[u][c] = p, u = fa[u];
18     if( !u ) fa[p] = 1;
19     else {
20         v = son[u][c];
21         if( dep[v] == dep[u] + 1 ) fa[p] = v;
22         else {
23             nv = ++tot; dep[nv] = dep[u] + 1;
24             fa[nv] = fa[v]; fa[v] = fa[p] = nv;
25             memcpy(son[nv],son[v],sizeof(son[v]));
26             while( son[u][c] == v ) son[u][c] = nv, u = fa[u]; 
27         }
28     } 
29 }
30 
31 int q[maxn<<1], tong[maxn<<1];
32 inline void pre() {
33     rep(i,1,tot) tong[dep[i]]++;
34     rep(i,1,tot) tong[i] += tong[i-1];
35     dow(i,tot,1) q[tong[dep[i]]--] = i;
36     int x;
37     dow(i,tot,1) x = q[i], sz[fa[x]] += sz[x];
38 
39 }
40 char c1[maxn]; ll dp[maxn<<1];
41 #define to i->to
42 inline void dfs(int x) {
43     dp[x] += 1ll * sz[x] * (dep[x] - dep[fa[x]]);
44     ez(i,x) dp[to] += dp[x], dfs(to);
45 }
46 
47 int main() {
48     scanf("%s", c1); int s1 = strlen(c1);
49     rep(i,0,s1-1) extend(c1[i]-'a');  
50     pre(); rep(i,1,tot) if( fa[i] ) add(fa[i],i); dfs(1);
51     //rep(i,1,tot) { rep(j,0,5) cout<52     //rep(i,1,tot) cout<
53     scanf("%s", c1); s1 = strlen(c1);
54     int t, p = 1, l = 0; ll ans = 0;
55     rep(i,0,s1-1) {
56         t = c1[i] - 'a';
57         while( p && !son[p][t] ) p = fa[p];
58         if( !p ) p = 1, l = 0;
59         else l = min(l,dep[p]) + 1, p = son[p][t];
60         ans += dp[fa[p]], ans += 1ll * (l - dep[fa[p]]) * sz[p];
61     }
62     cout<endl;
63     return 0;
64 }

 

转载于:https://www.cnblogs.com/83131yyl/p/5483535.html

更多相关:

  • 嗯~ o(* ̄▽ ̄*)o lca是树上两点的最近公共祖先。如果在同一个分支上就是更靠近根的那个点,否则就是大家一起向上走,第一次能都经过的那个点。 根据这两个性质,我们对于每次询问可以把一个向上走到根节点,标记走过的点。然后从另一个点向上走,直到遇到第一个标记过的点即为lca。 如果整个树是一个长链,这样的方法就会退化成一复杂度为n...

  •         Apache POI是一个开源的利用Java读写Excel,WORD等微软OLE2组件文档的项目。        我的需求是对Excel的数据进行导入或将数据以Excel的形式导出。先上简单的测试代码:package com.xing.studyTest.poi;import java.io.FileInputSt...

  • 要取得[a,b)的随机整数,使用(rand() % (b-a))+ a; 要取得[a,b]的随机整数,使用(rand() % (b-a+1))+ a; 要取得(a,b]的随机整数,使用(rand() % (b-a))+ a + 1; 通用公式:a + rand() % n;其中的a是起始值,n是整数的范围。 要取得a到b之间的...

  • 利用本征图像分解(Intrinsic Image Decomposition)算法,将图像分解为shading(illumination) image 和 reflectance(albedo) image,计算图像的reflectance image。 Reflectance Image 是指在变化的光照条件下能够维持不变的图像部分...

  • 题目:面试题39. 数组中出现次数超过一半的数字 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。 你可以假设数组是非空的,并且给定的数组总是存在多数元素。 示例 1: 输入: [1, 2, 3, 2, 2, 2, 5, 4, 2] 输出: 2 限制: 1 <= 数组长度 <= 50000 解题: cl...

  • 题目:二叉搜索树的后序遍历序列 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。 参考以下这颗二叉搜索树:      5     /    2   6   /  1   3示例 1: 输入: [1,6,3,2,5] 输出...