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<cstdio>
 2 #include<iostream>
 3 #include<cstring>
 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<<son[i][j]<<" "; cout<<endl; }
52     //rep(i,1,tot) cout<<sz[i]<<" "<<dp[i]<<" "<<dep[i]-dep[fa[i]]<<" "<<fa[i]<<endl;
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<<ans<<endl;
63     return 0;
64 }
时间: 2024-10-11 16:30:35

4566: [Haoi2016]找相同字符 SAM的相关文章

●BZOJ 4566 [Haoi2016]找相同字符

题链: http://www.lydsy.com/JudgeOnline/problem.php?id=4566 题解: 后缀数组,单调栈.把两个串A,B拼接起来,中间用没出现过的字符隔开.然后用倍增算法求出 sa[] rank[] height[]接着用单调栈维护出两个数组 L[],R[],意义如下:L[i]:表示在后缀数组中,排名最小(记其排名为 L[i])的后缀与排名为 i的后缀的LCP>=hei[i]同理 R[i]:表示在后缀数组中,排名最大(记其排名为 R[i])的后缀与排名为 i的后

[HAOI2016]找相同字符(SAM)

题目描述 给定两个字符串,求出在两个字符串中各取出一个子串使得这两个子串相同的方案数.两个方案不同当且仅当这两个子串中有一个位置不同. 输入输出格式 输入格式: 两行,两个字符串s1,s2,长度分别为n1,n2.1 <=n1, n2<= 200000,字符串中只有小写字母 输出格式: 输出一个整数表示答案 输入输出样例 输入样例#1: 复制 aabb bbaa 输出样例#1: 复制 10 这到题目是vj上一道题目的简化版对第一个串建立自动机 在拓扑一边球每个状态串的出现次数然后然第二个串在树上

bzoj 4566: [Haoi2016]找相同字符

Description 给定两个字符串,求出在两个字符串中各取出一个子串使得这两个子串相同的方案数.两个方案不同当且仅当这两 个子串中有一个位置不同. Input 两行,两个字符串s1,s2,长度分别为n1,n2.1 <=n1, n2<= 200000,字符串中只有小写字母 Output 输出一个整数表示答案 Sample Input aabb bbaa Sample Output 10 HINT Source 首先这种多个字符串的一般要拼成一个串: 如果考虑暴力的话,那就是开头在第一个串的后

BZOJ4566:[Haoi2016]找相同字符

4566: [Haoi2016]找相同字符 Time Limit: 20 Sec  Memory Limit: 256 MBSubmit: 545  Solved: 302[Submit][Status][Discuss] Description 给定两个字符串,求出在两个字符串中各取出一个子串使得这两个子串相同的方案数.两个方案不同当且仅当这两 个子串中有一个位置不同. Input 两行,两个字符串s1,s2,长度分别为n1,n2.1 <=n1, n2<= 200000,字符串中只有小写字母

[HAOI2016]找相同字符(广义SAM)

[HAOI2016]找相同字符(广义SAM) 题面 给定两个字符串,求出在两个字符串中各取出一个子串使得这两个子串相同的方案数.两个方案不同当且仅当这两个子串中有一个位置不同. 分析 此题有一个比较繁琐的后缀数组做法,但是用广义SAM可以秒杀. 把两个串建成广义SAM,对于每个后缀,记录\(endpos\)集合中落在第一个串中和第二个串中的位置个数,记为\(cnt_{x,0},cnt_{x,1}\). 对于自动机上的每个节点\(x\),出现位置方案数的贡献是\(cnt_{x,0} \cdot c

P3181 [HAOI2016]找相同字符

P3181 [HAOI2016]找相同字符 对一个串建SAM,另一个串在这上面跑,到达一点时,假设经过了\(cnt\)个点 计算这个串所有后缀产生的贡献就好了,直接暴力跑上去可能会超时,topsort预处理一下 #include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<algorithm> #incl

[HAOI2016]找相同字符(后缀数组+单调栈)

[HAOI2016]找相同字符(后缀数组+单调栈) 题面 给定两个字符串,求出在两个字符串中各取出一个子串使得这两个子串相同的方案数.两个方案不同当且仅当这两个子串中有一个位置不同. 分析 我们把两个字符串接在一起,中间加一个分隔符.如\(\text{AABB}\)和\(\text{BBAA}\)变成\(\text{AABB|BBAA}\).我们考虑两个相同字串,如\(\text{BB}\),它在新串中对应了两个后缀\(BB|BBAA\)和\(\text{BBAA}\)的LCP. 容易发现,LC

[HAOI2016]找相同字符

题目大意: 给你两个字符串a和b,要求从a和b中各取出一个相等的子串,问不同的取法有多少种. 思路: 对于a串建立SAM,然后DP求出每个状态Right集合的大小. 然后把b串放进去匹配,对于每一个匹配到的结点p,它的每一个Right状态都可以匹配一个长度为tmp-s[s[p].link].len的串,那么将s[p].right*(tmp-s[s[p].link].len)计入答案. 统计每个状态被匹配的次数,并累加进它Parent中. 将s[top[i]].right*f[top[i]]*(s

BZOJ4566:[HAOI2016]找相同字符——题解

https://www.lydsy.com/JudgeOnline/problem.php?id=4566 https://www.luogu.org/problemnew/show/P3181 给定两个字符串,求出在两个字符串中各取出一个子串使得这两个子串相同的方案数.两个方案不同当且仅当这两个子串中有一个位置不同. 广义后缀自动机,两个串各处理他们的size(或right?),然后对结点l排序,对于每个结点他们的size相乘即为答案. ……等等怎么WA了啊. 比如: aba abaa 这组数