CF.666E.Forensic Examination(广义后缀自动机 线段树合并)

题目链接

\(Description\)

给定串S和m个串Ti。Q次询问,每次询问l,r,pl,pr,求S[pl~pr]在Tl~Tr中的哪个串出现次数最多,输出最多次数及其T的下标。若有多个,输出下标最小的。

\(Solution\)

挺好的题吧

对T个串建SAM,然后要求出SAM每个节点上|right|最大的是哪个串。
每个节点的|right|可以在DFS parent树时合并子节点得到,如果用线段树维护,|right|最大的位置也可以合并得到。
这样可以离线处理询问,最后DFS一遍得到答案。
怎么得到S[pl~pr]在SAM上的匹配节点呢。
由S[1]到S[i]尽可能匹配,匹配不了就跳fa。这样能保证后缀(S[i])出现了。
所以在i这处理pr==i的询问。只要我们从当前节点一直跳fa,就能找到匹配S[pl,pr]的节点,其答案就是该节点的|right|状态。可以用倍增。
如果次数为0的话也要输出最靠前的(l)。可以。

唉 6点多写完代码调到现在 心累

//529ms 56700KB
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#define gc() getchar()
#define Bit 16
const int N=5e5+5,M=5e4+5,S=M<<1;

int m,root[S],fa[S][18];
char s[N],tmp[M];
struct Edge
{
    int Enum,H[N],nxt[N],to[N];
    inline void AddEdge(int u,int v){
        to[++Enum]=v, nxt[Enum]=H[u], H[u]=Enum;
    }
}Pos,Qy;
struct Edge2
{
    int Enum,H[S],nxt[S],to[S];
    inline void AddEdge(int u,int v){
        to[++Enum]=v, nxt[Enum]=H[u], H[u]=Enum;
    }
}Par;
struct Queries{
    int l,r,pl,pr;
}q[N];
struct Suffix_Automaton
{
    int tot,las,fa[S],son[S][26],len[S];

    Suffix_Automaton() {tot=las=1;}
    void Insert(int c)
    {
        int np=++tot,p=las; len[las=np]=len[p]+1;
        for(; p&&!son[p][c]; p=fa[p]) son[p][c]=np;
        if(!p) fa[np]=1;
        else
        {
            int q=son[p][c];
            if(len[q]==len[p]+1) fa[np]=q;
            else
            {
                int nq=++tot; len[nq]=len[p]+1;
                memcpy(son[nq],son[q],sizeof son[q]);
                fa[nq]=fa[q], fa[q]=fa[np]=nq;
                for(; son[p][c]==q; p=fa[p]) son[p][c]=nq;
            }
        }
    }
}sam;
struct Node
{
    int val,id;
    bool operator <(const Node &x)const{
        return val<x.val||(val==x.val&&id>x.id);
    }
}Ans[N];
struct Segment_Tree
{
    #define S M*17
    #define lson son[x][0]
    #define rson son[x][1]
    int tot,son[S][2];
    Node node[S];
    #undef S
    #define Update(x) node[x]=std::max(node[lson],node[rson]);
    void Insert(int &x,int l,int r,int pos)
    {
        x=++tot;
        if(l==r) return (void)(node[x]=(Node){1,pos});
        int m=l+r>>1;
        pos<=m ? Insert(lson,l,m,pos) : Insert(rson,m+1,r,pos);
        Update(x);//Update
    }
    int Merge(int x,int y)
    {
        if(!x||!y) return x^y;
        if(!lson&&!rson) return node[x].val+=node[y].val, x;//叶节点,合并right
        lson=Merge(lson,son[y][0]), rson=Merge(rson,son[y][1]);
        Update(x); return x;
    }
    Node Query(int x,int l,int r,int L,int R)
    {
        if(!x) return (Node){0,L};
        if(L<=l && r<=R) return node[x];
        int m=l+r>>1;
        if(L<=m)
            if(m<R) return std::max(Query(lson,l,m,L,R),Query(rson,m+1,r,L,R));
            else return Query(lson,l,m,L,R);
        return Query(rson,m+1,r,L,R);
    }
}T;

inline int read()
{
    int now=0;register char c=gc();
    for(;!isdigit(c);c=gc());
    for(;isdigit(c);now=now*10+c-'0',c=gc());
    return now;
}
void DFS(int x)
{
    for(int i=Par.H[x]; i; i=Par.nxt[i])
        DFS(Par.to[i]), root[x]=T.Merge(root[x],root[Par.to[i]]);
    for(int i=Pos.H[x],id; i; i=Pos.nxt[i])
        id=Pos.to[i], Ans[id]=T.Query(root[x],1,m,q[id].l,q[id].r);
}

int main()
{
    scanf("%s",s+1); int n=strlen(s+1);
    m=read();
    for(int i=1; i<=m; ++i)
    {
        scanf("%s",tmp), sam.las=1;
        for(int j=0,l=strlen(tmp); j<l; ++j)
            sam.Insert(tmp[j]-'a'), T.Insert(root[sam.las],1,m,i);//不就是每位的|right[las]|=1吗→_→我在纠结什么
    }
    int Q=read();
    for(int i=1; i<=Q; ++i)
        q[i]=(Queries){read(),read(),read(),read()}, Qy.AddEdge(q[i].pr,i);
    int lim=sam.tot;
    for(int x=2; x<=lim; ++x) Par.AddEdge(fa[x][0]=sam.fa[x],x);
    for(int i=1; i<=Bit; ++i)
        for(int x=2; x<=lim; ++x)
            fa[x][i]=fa[fa[x][i-1]][i-1];
    for(int c,now=0,p=1,i=1; i<=n; ++i)
    {
        if(sam.son[p][c=s[i]-'a']) p=sam.son[p][c], ++now;//!!!靠 这写错调了两个小时 唉 心累
        else
        {
            for(c=s[i]-'a'; p&&!sam.son[p][c]; p=sam.fa[p]);
            if(!p) {p=1, now=0; continue;}
            now=sam.len[p]+1, p=sam.son[p][c];
        }
        for(int j=Qy.H[i],len,id; j; j=Qy.nxt[j])
        {
            id=Qy.to[j];
            if(now<(len=q[id].pr-q[id].pl+1)) continue;
            int x=p;
            for(int i=Bit; ~i; --i)
                if(sam.len[fa[x][i]]>=len) x=fa[x][i];
            Pos.AddEdge(x,id);
        }
    }
    DFS(1);
    for(int i=1; i<=Q; ++i)
        if(!Ans[i].val) printf("%d 0\n",q[i].l);
        else printf("%d %d\n",Ans[i].id,Ans[i].val);

    return 0;
}

原文地址:https://www.cnblogs.com/SovietPower/p/9368392.html

时间: 2024-09-30 12:49:39

CF.666E.Forensic Examination(广义后缀自动机 线段树合并)的相关文章

CF666E Forensic Examination(广义后缀自动机+线段树合并)

Luogu 给你一个串 $ S $ 以及一个字符串数组 $ T_1 ~ T_m $ , $ q $ 次询问,每次问 $ S $ 的子串S[p_l,p_r]在 $ T_l ~ T_r $ 中的哪个串里的出现次数最多,并输出出现次数. 如有多解输出最靠前的那一个. 题解时间 SAM的毒瘤题,无论是倍增来满足长度限制,线段树合并来求区间询问,应有尽有... 对于 $ T $ 串建广义SAM,之后考虑如何使得 $ S $ 在SAM上匹配时求出 $ S $ 在每个 $ T $ 的出现次数. 很明显用线段树

CF666E Forensic Examination(后缀自动机+线段树合并)

给你一个串S以及一个字符串数组T[1..m],q次询问,每次问S的子串S[pl..pr]在T[l..r]中的哪个串里的出现次数最多,并输出出现次数. 如有多解输出最靠前的那一个. 我们首先对m个字符串数组建出后缀自动机,然后我们可以通过跳trans边找到S前i个字符代表的前缀的最长后缀.我们要找的是S[pl..pr]并不是以pr结束最长的后缀,但我们可以确定S[pl..pr]一定是当前点的祖先所以当我们跳到pr代表的点时我们倍增往上跳知道找到一个点的长度刚好大于等于pr-pl+1,这个点就是询问

【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$

Codeforces 666E Forensic Examination SAM+权值线段树

第一次做这种$SAM$带权值线段树合并的题 然而$zjq$神犇看完题一顿狂码就做出来了 $Orz$ 首先把所有串当成一个串建$SAM$ 我们对$SAM$上每个点 建一棵权值线段树 每个叶子节点表示一个匹配串能到达这个点的子串个数 这样我们对最后的$SAM$的权值线段树按$parent$树合并 询问的时候找到对应的$SAM$上的权值线段树直接查询就好了 具体的操作看代码吧~ #include<bits/stdc++.h> using namespace std; #define FO(x) {f

【XSY1551】往事 广义后缀数组 线段树合并

题目大意 给你一颗trie树,令\(s_i\)为点\(i\)到根的路径上的字符组成的字符串.求\(max_{u\neq v}(LCP(s_u,s_v)+LCS(s_u,s_v))\) \(LCP=\)最长公共前缀,\(LCS=\)最长公共后缀 \(1\leq n\leq 200000\),字符集为\(\{0\ldots 300\}\) 题解 我们先看看这个\(LCP(s_u,s_v)\)怎么求 广义后缀自动机不行.广义后缀树可能可以,但我不会.广义后缀数组可以.然后我就开始手推广义后缀数组 广义

HDU - 6704 K-th occurrence (后缀数组+主席树/后缀自动机+线段树合并+倍增)

题意:给你一个长度为n的字符串和m组询问,每组询问给出l,r,k,求s[l,r]的第k次出现的左端点. 解法一: 求出后缀数组,按照排名建主席树,对于每组询问二分或倍增找出主席树上所对应的的左右端点,求第k大的下标即可. 1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=1e5+10,mod=998244353; 5 char buf[N]; 6 int s[N],sa[

CF700E:Cool Slogans(后缀自动机,线段树合并)

Description 给你一个字符串,如果一个串包含两个不重叠的相同子串,那么这个串的价值就是子串的价值+1.问你给定字符串的最大价值子串的价值. Input 第一行读入字符串长度$n$,第二行是字符串. Output 一行答案. Sample Input1 3abc Sample Output1 1 Sample Input2 5ddddd Sample Output2 5 Sample Input3 11abracadabra Sample Output3 3 Solution 首先把后缀

bzoj 3413: 匹配 后缀自动机+线段树合并

并不是很难啊,把细节想好了再写就很轻松了~ code: #include <bits/stdc++.h> #define N 200003 #define LL long long #define setIO(s) freopen(s".in","r",stdin) ,freopen(s".out","w",stdout) using namespace std; struct SAM { int tot,last

CF666E Forensic Examination SAM+倍增,线段树和并

题面: 给你一个串S以及一个字符串数组T[1..m],q次询问,每次问S的子串S[p_l..p_r]在T[l..r]中的哪个串里的出现次数最多,并输出出现次数.如有多解输出最靠前的那一个. 分析: 第一次见这道题时,由于对算法十分陌生,就将其压进了任务计划,这一天又提到了这道题,这才算是重见天日. 数据结构题,越来越注重多种数据结构的搭配使用.搭配使用呢,我们就要知道各种数据结构的功能与适用范围,下面是这道题一个理想的思考过程(虽然我是抄的题解--) 首先,一个模式串的区间要在多个串上进行匹配,