bzoj4566:[Haoi2016]找相同字符 广义后缀自动机

bzoj4566: [Haoi2016]找相同字符

Description

给定两个字符串,求出在两个字符串中各取出一个子串使得这两个子串相同的方案数。两个方案不同当且仅当这两
个子串中有一个位置不同。

Input

两行,两个字符串s1,s2,长度分别为n1,n2。1 <=n1, n2<= 200000,字符串中只有小写字母

Output

输出一个整数表示答案

Sample Input

aabb
bbaa

Sample Output

10

分析

广义后缀自动机。
其实广义后缀自动机这个东西就是把后缀自动机扩展到了多主串的情况。和原来的自动机基本一样。
广义后缀自动机方法和后缀自动机基本一样,主要是多了Trie树,所以增量时的起始节点要变。
如果是在Trie树上跑的话,一个节点插入后缀自动机的时候的last就是其在Trie树上的父亲。插入的时候注意一下记录last就可以了。
后缀自动机本身其实也可以看成一颗前缀树。
每次插入主串的时候吧last重置,每插入一个字符,判断一下Sam之前有没有插入过这个节点,具体的判断方法是ch[p][c]=Ture && mx[p]+1==mx[ch[p][c]]也就是p和ch[p][c]的Right集合是一样的,这个时候后缀自动机之前一定插入过ch[p][c]这个字符。
还有拓扑序注意一下,看注释吧。

代码

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<map>
using namespace std;
const int N = 8e5+50;
int fa[N], len[N], last, sz, n, ch[N][26], c[N], q[N];
long long ans, val[N][2];

void Sam_Extend(int c, bool num) {
    int p = last;
    if(ch[p][c] && len[ch[p][c]] == len[p] + 1) {++val[last = ch[p][c]][num]; return ;} //增量的时候判断一下之前后缀自动机中有没有插入过这个前缀。
    int np = last = ++sz;
    len[np] = len[p] + 1; ++val[np][num];
    for(;p && !ch[p][c]; p = fa[p]) ch[p][c] = np;
    if(!p) fa[np] = 1;
    else {
        int q = ch[p][c];
        if(len[q] == len[p] + 1) fa[np] = q;
        else {
            int nq = ++sz; len[nq] = len[p] + 1;
            memcpy(ch[nq], ch[q], sizeof(ch[q]));
            fa[nq] = fa[q];
            fa[np] = fa[q] = nq;
            for(;ch[p][c] == q; p = fa[p]) ch[p][c] = nq;
        }
    }
}

void Sam_Build(bool p) {
    last = 1;
    char ch = getchar();
    while(ch < 'a' || ch > 'z') ch = getchar();
    for(;ch >= 'a' && ch <= 'z'; ch = getchar()) Sam_Extend(ch - 'a', p), ++n;
}

void work() {
    for(int i = 1;i <= sz; ++i) ++c[len[i]];
    for(int i = 1;i <= n; ++i) c[i] += c[i - 1];
    for(int i = 1; i <= sz; ++i) q[c[len[i]]--] = i; //这里一定不能从sz到i搜索,我也不知道为什么,如果有大佬看到求解答。
    for(int i = sz; i; --i)
        val[fa[q[i]]][0] += val[q[i]][0], val[fa[q[i]]][1] += val[q[i]][1];
    for(int i = 1;i <= sz; ++i) ans += 1LL * (len[i] - len[fa[i]]) * val[i][0] * val[i][1];
    printf("%lld\n", ans);
}

int main() {
    ++sz; 
    Sam_Build(0); 
    Sam_Build(1);
    work();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值