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,这个点就是询问区间代表的点。
那么我们怎么求答案呢?上线段树合并就行(线段树以[1,m]为值域),这就要求我们对询问离线。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
const int N=501000;
int n,m,L;
char S[N],s[N];
int cnt,head[N];
struct edge{
    int to,nxt;
}e[N];
void add(int u,int v){
    cnt++;
    e[cnt].nxt=head[u];
    e[cnt].to=v;
    head[u]=cnt;
}
struct ques{
    int l,a,b,id;
    ques(int ll=0,int aa=0,int bb=0,int idx=0){
        l=ll;a=aa;b=bb;id=idx;
    }
};
struct data{
    int mx,id;
    data(int mxx=0,int idx=0){
        mx=mxx;id=idx;
    }
}ans[N];
struct qu{
    int a,b,id;
    qu(int idx=0,int aa=0,int bb=0){
        a=aa;b=bb;id=idx;
    }
};
vector<qu> que[N];
data max(data a,data b){
    if(a.mx==b.mx){
        if(a.id<b.id)return a;
        return b;
    }
    if(a.mx>b.mx)return a;
    return b;
}
vector<ques> vec[N];
struct sam{
    int tot,u,trans[N][27],fa[N][23],len[N];
    int root[N],ch[N*50][2],cnt,mx[N*50],id[N*50];
    void init(){tot=u=1;}
    void rebuild(){u=1;}
    void ins(int k,int c){
        if(trans[u][c]){
            int v=trans[u][c];
            if(len[v]==len[u]+1)u=v,add(1,m,k,root[v]);
            else{
                int x=++tot;add(1,m,k,root[x]);len[x]=len[u]+1;
                memcpy(trans[x],trans[v],sizeof(trans[v]));
                fa[x][0]=fa[v][0];fa[v][0]=x;
                for(;u&&trans[u][c]==v;u=fa[u][0])trans[u][c]=x;
                u=x;
            }
        }
        else{
            int x=++tot;add(1,m,k,root[x]);len[x]=len[u]+1;
            for(;u&&trans[u][c]==0;u=fa[u][0])trans[u][c]=x;
            if(u==0)fa[x][0]=1;
            else{
                int v=trans[u][c];
                if(len[v]==len[u]+1)fa[x][0]=v;
                else{
                    int w=++tot;
                    len[w]=len[u]+1;
                    fa[w][0]=fa[v][0];
                    memcpy(trans[w],trans[v],sizeof(trans[w]));
                    fa[x][0]=fa[v][0]=w;
                    for(;u&&trans[u][c]==v;u=fa[u][0])trans[u][c]=w;
                }
            }
            u=x;
        }
    }
    void update(int now){
        if(mx[ch[now][0]]>=mx[ch[now][1]])mx[now]=mx[ch[now][0]],id[now]=id[ch[now][0]];
        else mx[now]=mx[ch[now][1]],id[now]=id[ch[now][1]];
    }
    void add(int l,int r,int x,int &now){
        if(now==0)now=++cnt;
        if(l==r){mx[now]++;id[now]=l;return;}
        int mid=(l+r)>>1;
        if(x>mid)add(mid+1,r,x,ch[now][1]);
        else add(l,mid,x,ch[now][0]);
        update(now);
    }
    data check(int l,int r,int L,int R,int now){
        if(l==L&&r==R)return (data(mx[now],id[now]));
        int mid=(l+r)>>1;
        if(L>mid)return check(mid+1,r,L,R,ch[now][1]);
        else if(R<=mid)return check(l,mid,L,R,ch[now][0]);
        else return max(check(l,mid,L,mid,ch[now][0]),check(mid+1,r,mid+1,R,ch[now][1]));
    }
    void merge(int l,int r,int &x,int y){
        if(!x||!y){x=x|y;return;}
        if(l==r){mx[x]+=mx[y];return;}
        int mid=(l+r)>>1;
        merge(l,mid,ch[x][0],ch[y][0]);
        merge(mid+1,r,ch[x][1],ch[y][1]);
        update(x);
    }
    void dfs(int u){
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].to;
            dfs(v);
            merge(1,m,root[u],root[v]);
        }
        for(int i=0;i<que[u].size();i++){
            data a=check(1,m,que[u][i].a,que[u][i].b,root[u]);
            if(a.mx==0)continue;
            ans[que[u][i].id]=a;
        }
    }
    void work(){
        int now=1;
        int lon=0;
        for(int i=1;i<=L;i++){
            while(trans[now][S[i]-'a'+1]==0&&now)now=fa[now][0],lon=len[now];
            if(now==0){
                for(int j=0;j<vec[i].size();j++)ans[vec[i][j].id].id=vec[i][j].a;
                now=1;lon=0;continue;
            }
            now=trans[now][S[i]-'a'+1];lon++;
            for(int j=0;j<vec[i].size();j++){
                ans[vec[i][j].id].id=vec[i][j].a;
                if(lon<i-vec[i][j].l+1)continue;
                int x=now;
                for(int k=20;k>=0;k--)
                    if(len[fa[x][k]]>=i-vec[i][j].l+1)x=fa[x][k];
                qu a=qu(vec[i][j].id,vec[i][j].a,vec[i][j].b);
                que[x].push_back(a);
            }
        }
    }
}sam;
int read(){
    int sum=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
    return sum*f;
}
int main(){
    scanf("%s",S+1);
    scanf("%d",&m);
    sam.init();
    for(int i=1;i<=m;i++){
        scanf("%s",s+1);
        int len=strlen(s+1);
        for(int j=1;j<=len;j++)sam.ins(i,s[j]-'a'+1);
        sam.rebuild();
    }
    for(int i=2;i<=sam.tot;i++)add(sam.fa[i][0],i);
    for(int j=1;j<=20;j++)
        for(int i=2;i<=sam.tot;i++)
            sam.fa[i][j]=sam.fa[sam.fa[i][j-1]][j-1];
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        int c=read(),d=read(),a=read(),b=read();
        vec[b].push_back(ques(a,c,d,i));
    }
    L=strlen(S+1);
    sam.work();
    sam.dfs(1);
    for(int i=1;i<=n;i++)printf("%d %d\n",ans[i].id,ans[i].mx);
    return 0;
}

原文地址:https://www.cnblogs.com/Xu-daxia/p/10229791.html

时间: 2024-10-09 14:54:03

CF666E 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\),再给出 \(n\) 个串 \(T_i\), \(q\) 次询问 \(S[pl,pr]\) 在 $ T_{[l,r]}$哪个串出现次数最多. solution: 不难想到我们找 \(S[pl,pr]\) 是可以记录 \(ed_{pr}\) 然后倍增上去找到这个区间所对应的 SAM 节点. 我们把 \(T_i\) 插入 SAM 里,并且对应节点搞上 \(i\),然后合并就好了qwq. SAM 某个子树部分都是包含他自己的串,所以线段树合并一下就变成了子树数颜色以及找到

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

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一遍得到答案.

【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)\)怎么求 广义后缀自动机不行.广义后缀树可能可以,但我不会.广义后缀数组可以.然后我就开始手推广义后缀数组 广义

[BZOJ1396]识别子串 后缀自动机+线段树

1396: 识别子串 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 451  Solved: 290[Submit][Status][Discuss] Description Input 一行,一个由小写字母组成的字符串S,长度不超过10^5 Output L行,每行一个整数,第i行的数据表示关于S的第i个元素的最短识别子串有多长. Sample Input agoodcookcooksgoodfood Sample Output 1 2 3 3

BZOJ1396&amp;2865 识别子串 【后缀自动机 + 线段树】

题目 输入格式 一行,一个由小写字母组成的字符串S,长度不超过10^5 输出格式 L行,每行一个整数,第i行的数据表示关于S的第i个元素的最短识别子串有多长. 输入样例 agoodcookcooksgoodfood 输出样例 1 2 3 3 2 2 3 3 2 2 3 3 2 1 2 3 3 2 1 2 3 4 题解 BZOJ AC200纪念,, 这两题题干是一样的,但唯一不同的是..后者卡空间[MLE得飞起] 先说解法: 我们知道后缀自动机上的parent树的每个节点子树中叶子的数量就是该节点