E. Three strings 广义后缀自动机

http://codeforces.com/problemset/problem/452/E

多个主串的模型。

建立一个广义后缀自动机,可以dp出每个状态的endpos集合大小。同时也维护一个R[]表示那个串出现过。

所以可以算出每个状态的dp[i][k]表示第k个串在第i个状态中出现的次数。

可以知道sigma dp[i][0...k]是等于  endpos集合的大小。

然后把这个贡献加到min(i)....max(i)中去就可以了

差分一下。

#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false)
using namespace std;
#define inf (0x3f3f3f3f)
typedef long long int LL;
const int MOD = 1e9 + 7;
const int maxn = 6e5 + 20, N = 26;
struct Node {
    int mxCnt; //mxCnt表示后缀自动机中当前节点识别子串的最大长度
    int miCnt; //miCnt表示后缀自动机中当前节点识别子串的最小长度
    int id; //表示它是第几个后缀自动机节点,指向了它,但是不知道是第几个,用id判断
    int pos; //pos表示它在原串中的位置。
    bool flag; //表示当前节点是否能识别前缀
    bool R[3]; // 广义后缀自动机识别此状态是否在第R[i]个主串中出现过
    struct Node *pNext[N], *fa;
}suffixAutomaton[maxn], *root, *last; //大小需要开2倍,因为有一些虚拟节点
int t;  //用到第几个节点
struct Node *create(int mxCnt = -1, struct Node *node = NULL) { //新的节点
    if (mxCnt != -1) {
        suffixAutomaton[t].mxCnt = mxCnt, suffixAutomaton[t].fa = NULL;
        for (int i = 0; i < N; ++i) suffixAutomaton[t].pNext[i] = NULL;
    } else {
        suffixAutomaton[t] = *node; //保留了node节点所有的指向信息。★全部等于node
        //可能需要注意下pos,在原串中的位置。现在pos等于原来node的pos
    }
    suffixAutomaton[t].id = t;  //必须要有的,不然id错误
    suffixAutomaton[t].flag = false; //默认不是前缀节点
    return &suffixAutomaton[t++];
}
void addChar(int x, int pos, int id) { //pos表示在原串的位置
    struct Node *p = last;
    if (p->pNext[x] != NULL) { // 有了,就不需要np,广义后缀自动机
        struct Node *q = p->pNext[x];
        if (p->mxCnt + 1 == q->mxCnt) {
            last = q; //用来接收后缀字符
            q->flag = true;
            q->R[id] = true;
            return;
        }
        //现在的q没办法成为接受后缀的点
        //那么就开一个节点模拟它,所以这个节点是id的前缀节点
        struct Node * nq = create(-1, q);
        for (int i = 0; i < 3; ++i) nq->R[i] = false;
        nq->mxCnt = p->mxCnt + 1;
        nq->R[id] = true;
        nq->flag = true; //这个点是属于id的。是id的前缀节点,因为q不能接受后缀
        q->fa = nq; //这里是没有np的
        q->miCnt = nq->mxCnt + 1;
        for (; p && p->pNext[x] == q; p = p->fa) p->pNext[x] = nq;
        last = nq; //成为接受后缀的节点。
        return;
    }
    struct Node *np = create(p->mxCnt + 1, NULL);
    for (int i = 0; i < 3; ++i) np->R[i] = false; //每次都要清空
    np->R[id] = true;
    np->flag = true; //前缀节点
    np->pos = pos, last = np; //last是最尾那个可接收后缀字符的点。
    for (; p != NULL && p->pNext[x] == NULL; p = p->fa) p->pNext[x] = np;
    if (p == NULL) {
        np->fa = root;
        np->miCnt = 1; // 从根节点引一条边过来
        return;
    }
    struct Node *q = p->pNext[x];
    if (q->mxCnt == p->mxCnt + 1) { //中间没有任何字符,可以用来代替接受后缀、
        np->fa = q;
        np->miCnt = q->mxCnt + 1; // q是状态8的"ab",np是状态7的"bab"长度是2+1
        return;
    }
    struct Node *nq = create(-1, q); // 新的q节点,用来代替q,帮助np接收后缀字符
    for (int i = 0; i < 3; ++i) nq->R[i] = false;
    nq->mxCnt = p->mxCnt + 1; //就是需要这样,这样中间不包含任何字符
    q->miCnt = nq->mxCnt + 1, np->miCnt = nq->mxCnt + 1;
    q->fa = nq, np->fa = nq; //现在nq是包含了本来q的所有指向信息
    for (; p && p->pNext[x] == q; p = p->fa) {
        p->pNext[x] = nq;
    }
}
void init() {
    t = 0;
    root = last = create(0, NULL);
}
char str[maxn];
LL dp[maxn][3];
queue<int> que;
int in[maxn];
LL ans[maxn];
void work() {
    init();
    int len = inf;
    for (int i = 0; i < 3; ++i) {
        last = root;
        scanf("%s", str + 1);
        int t = 0;
        for (int j = 1; str[j]; ++j) {
            t++;
            addChar(str[j] - ‘a‘, j, i);
        }
        len = min(len, t);
    }
    for (int i = 1; i < t; ++i) {
        in[suffixAutomaton[i].fa->id]++;
//        if (suffixAutomaton[i].flag) {
            for (int j = 0; j < 3; ++j) {
                dp[i][j] = suffixAutomaton[i].R[j];
            }
//        }
    }
    for (int i = 1; i < t; ++i) {
        if (in[i] == 0) {
            que.push(i);
        }
    }
    while (!que.empty()) {
        int cur = que.front();
        que.pop();
        if (!cur) break;
        for (int i = 0; i < 3; ++i) {
            dp[suffixAutomaton[cur].fa->id][i] += dp[cur][i];
        }
        in[suffixAutomaton[cur].fa->id]--;
        if (in[suffixAutomaton[cur].fa->id] == 0) que.push(suffixAutomaton[cur].fa->id);
    }
    for (int i = 1; i < t; ++i) {
        LL res = 1;
        for (int j = 0; j < 3; ++j) {
            res = res * dp[i][j] % MOD;
        }
//        printf("%lld ", res);
        int en = suffixAutomaton[i].mxCnt;
        int be = suffixAutomaton[i].miCnt;
        ans[en + 1] = (ans[en + 1] - res + MOD) % MOD;
        ans[be] = (ans[be] + res) % MOD;
    }
//    printf("\n");
    for (int i = 1; i <= len; ++i) {
        ans[i] = (ans[i] + ans[i - 1] + MOD) % MOD;
    }
    for (int i = 1; i <= len; ++i) {
        printf("%I64d ", ans[i]);
    }
}

int main() {
#ifdef local
    freopen("data.txt", "r", stdin);
//    freopen("data.txt", "w", stdout);
#endif
    work();
    return 0;
}

时间: 2024-08-04 18:59:27

E. Three strings 广义后缀自动机的相关文章

codeforces E. Little Elephant and Strings(广义后缀自动机,Parent树)

传送门在这里. 大意: 给一堆字符串,询问每个字符串有多少子串在所有字符串中出现K次以上. 解题思路: 这种子串问题一定要见后缀自动机Parent树Dfs序统计出现次数都是套路了吧. 这道题统计子串个数,那么可以发现,若一个节点所对应的子串出现了K次,那么其贡献就是len,不需要考虑重复. 因为即使出现重复也是在两个位置. 那么只需统计以每个点结束的子串就好了. 之前的Dfs序就很套路了. 只需再跑一遍字符串,更新答案就好了. 代码: 1 #include<cstdio> 2 #include

CodeForces-204E:Little Elephant and Strings (后缀自动机)

The Little Elephant loves strings very much. He has an array a from n strings, consisting of lowercase English letters. Let's number the elements of the array from 1 to n, then let's denote the element number i as ai. For each string ai (1 ≤ i ≤ n) t

【BZOJ3926】[Zjoi2015]诸神眷顾的幻想乡 广义后缀自动机

[BZOJ3926][Zjoi2015]诸神眷顾的幻想乡 Description 幽香是全幻想乡里最受人欢迎的萌妹子,这天,是幽香的2600岁生日,无数幽香的粉丝到了幽香家门前的太阳花田上来为幽香庆祝生日. 粉丝们非常热情,自发组织表演了一系列节目给幽香看.幽香当然也非常高兴啦. 这时幽香发现了一件非常有趣的事情,太阳花田有n块空地.在过去,幽香为了方便,在这n块空地之间修建了n-1条边将它们连通起来.也就是说,这n块空地形成了一个树的结构. 有n个粉丝们来到了太阳花田上.为了表达对幽香生日的祝

BZOJ 3277 串 (广义后缀自动机)

3277: 串 Time Limit: 10 Sec Memory Limit: 128 MB Submit: 309 Solved: 118 [Submit][Status][Discuss] Description 字符串是oi界常考的问题.现在给定你n个字符串,询问每个字符串有多少子串(不包括空串)是所有n个字符串中至少k个字符串的子串(注意包括本身). Input 第一行两个整数n,k. 接下来n行每行一个字符串. Output 输出一行n个整数,第i个整数表示第i个字符串的答案. Sa

广义后缀自动机

1).自动机的介绍 首先我们先来介绍一下什么是自动机,有限状态自动机的功能是识别字符串,令一个自动机A,若他能识别字符串S,就记为A(S)=Ture,否则A(S)=False. 自动机由五个部分组成,alpha:字符集,state:状态集合,init:初始状态,end:结束状态集合,trans:状态转移函数. 令trans(s,ch)表示当前状态是s,在读入字符ch之后,所到达的状态.如果trans(s,ch)这个转移不存在,为了方便,设其为null,同时null只能转移到null.null表示

hdu 5853 Jong Hyok and String(广义后缀自动机)

题目链接:hdu 5853 Jong Hyok and String 题意: 给你n个字符串,m个询问,每次询问一个字符串 定义set(s)={(i,j)} 表示 s在第i个字符串中出现,且末尾位置为j. 对于一个询问,求set(Qi)=set(t) ,t串的数量. 题解: 如果是n=1,那么就是后缀自动机的一道裸题,答案就是Qi串匹配的最后一个节点x,ml[x]-ml[f[x]]. 现在是多个串,那么就建立一个广义后缀自动机.每次插入一个串后,将last=root,然后继续插下一个就行了. 最

【BZOJ2806】【CTSC2012】Cheat 广义后缀自动机+二分+Dp

题目 题目在这里 思路&做法 我们先对标准作文库建广义后缀自动机. 然后对于每一篇阿米巴的作文, 我们首先把放到广义后缀自动机跑一遍, 对于每一个位置, 记录公共子串的长度\((\)即代码和下文中的\(val\)数组\()\) 接着我们二分答案, 用DP检验. Dp方程很好想, \(d_i = max \{ d_j + i - j \ | \ i-val_i <= j <= i-lim \}\) 可以用单点队列优化. 代码 #include <iostream> #incl

【codeforces666E】Forensic Examination 广义后缀自动机+树上倍增+线段树合并

题目描述 给出 $S$ 串和 $m$ 个 $T_i$ 串,$q$ 次询问,每次询问给出 $l$ .$r$ .$x$ .$y$ ,求 $S_{x...y}$ 在 $T_l,T_{l+1},...,T_r$ 中的哪一个里出现次数最多,输出出现次数最多的串编号(如果有多个则输出编号最小的)以及相应出现次数. $|S|,q\le 5\times 10^5$ ,$\sum\limits_{i=1}^m|T_i|\le 5\times 10^4$ . 题解 广义后缀自动机+树上倍增+线段树合并 对 $S$

BZOJ 3926: [Zjoi2015]诸神眷顾的幻想乡 广义后缀自动机 后缀自动机 字符串

https://www.lydsy.com/JudgeOnline/problem.php?id=3926 广义后缀自动机是一种可以处理好多字符串的一种数据结构(不像后缀自动机只有处理一到两种的时候比较方便). 后缀自动机可以说是一种存子串的缩小点数的trie树,广义后缀自动机就是更改了一下塞点的方式让它可以塞多个子串. 1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<