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 $ 的出现次数。

很明显用线段树合并就可以搞:

每插入 $ T_i $ 的一个字符就在 $ fin $ 上挂上一个 $ i $ 。

然后跳parent树搞线段树合并就好。

之后,由于每次 $ S $ 也只选中一部分,所以考虑:

对于一组询问 $ [l,r,pl,pr] $ ,首先毫无疑问将询问挂在 $ S $ 的 $ pr $ 位上。

之后匹配到 $ pr $ 位时,倍增跳现所处点 $ px $ 的 $ pre $ 到最上面的 $ x $ 使得 $ len[x]>=(pr-pl+1) $ 。

然后线段树上求答案即可。

#include<bits/stdc++.h>
using namespace std;
namespace RKK
{
const int N=1000011;
int n,m,qaq;
char s0[N],s1[N];int l0,l1;
struct sumireko{int to,ne;}e[N];int he[N],ecnt;
void addline(int f,int t){e[++ecnt].to=t;e[ecnt].ne=he[f];he[f]=ecnt;}
int fa[N][22];
int tcnt,rt[N],lson[N<<3],rson[N<<3];
struct pat
{
    int first,second;
    bool operator < (const pat &a)const{return second==a.second?first>a.first:second<a.second;}
};
pat ma[N<<3],ans[N];
void fuckup(int px){ma[px]=max(ma[lson[px]],ma[rson[px]]);}
void insert(int x,int &px,int pl,int pr)
{
    if(!px) px=++tcnt;
    if(pl==pr)
    {
        ma[px].first=x,ma[px].second++;
        return;
    }
    int pm=pl+pr>>1;
    if(x<=pm) insert(x,lson[px],pl,pm);
    else insert(x,rson[px],pm+1,pr);
    fuckup(px);
}
int merge(int px,int py,int pl,int pr)
{
    if(!px||!py) return px|py;
    int pz=++tcnt;
    if(pl==pr){ma[pz]=(pat){pl,ma[px].second+ma[py].second};}
    else
    {
        int pm=pl+pr>>1;
        lson[pz]=merge(lson[px],lson[py],pl,pm);
        rson[pz]=merge(rson[px],rson[py],pm+1,pr);
        fuckup(pz);
    }
    return pz;
}
void query(int l,int r,int px,int pl,int pr,pat &pa)
{
    if(!px) return;
    if(l<=pl&&r>=pr){pa=max(pa,ma[px]);return;}
    int pm=pl+pr>>1;
    if(l<=pm) query(l,r,lson[px],pl,pm,pa);
    if(r>pm) query(l,r,rson[px],pm+1,pr,pa);
}
struct ques{int p,le,l,r,id;};
vector<ques> ve[N];
struct remilia{int tranc[26],len,pre;};
struct sakuya
{
    remilia s[N];
    int fin,size;
    sakuya(){fin=size=1;}
    void ins(int ch)
    {
        int npx,npy,lpx,lpy;
        if(s[fin].tranc[ch])
        {
            lpx=fin,lpy=s[fin].tranc[ch];
            if(s[lpy].len==s[lpx].len+1) fin=lpy;
            else
            {
                npy=++size;
                s[npy]=s[lpy];
                s[npy].len=s[lpx].len+1;
                s[lpy].pre=npy;
                while(s[lpx].tranc[ch]==lpy)
                {
                    s[lpx].tranc[ch]=npy;
                    lpx=s[lpx].pre;
                }
                fin=npy;
            }
            return;
        }
        npx=++size;
        s[npx].len=s[fin].len+1;
        for(lpx=fin;lpx&&!s[lpx].tranc[ch];lpx=s[lpx].pre) s[lpx].tranc[ch]=npx;
        if(!lpx) s[npx].pre=1;
        else
        {
            lpy=s[lpx].tranc[ch];
            if(s[lpy].len==s[lpx].len+1) s[npx].pre=lpy;
            else
            {
                npy=++size;
                s[npy]=s[lpy];
                s[npy].len=s[lpx].len+1;
                s[npx].pre=s[lpy].pre=npy;
                while(s[lpx].tranc[ch]==lpy)
                {
                    s[lpx].tranc[ch]=npy;
                    lpx=s[lpx].pre;
                }
            }
        }
        fin=npx;
    }
    void dfs(int x)
    {
        for(int i=he[x],t=e[i].to;i;i=e[i].ne,t=e[i].to)
        {
            dfs(t),rt[x]=merge(rt[x],rt[t],1,m);
        }
    }
    void work()
    {
        for(int i=2;i<=size;i++) fa[i][0]=s[i].pre,addline(s[i].pre,i);
        for(int k=1;k<=21;k++)for(int i=2;i<=size;i++) fa[i][k]=fa[fa[i][k-1]][k-1];
        dfs(1);
        int px=1,lnow=0;
        for(int i=1;i<=l0;i++)
        {
            while(px&&!s[px].tranc[s0[i]-'a']) px=s[px].pre,lnow=s[px].len;
            if(!px) px=1,lnow=0;
            else px=s[px].tranc[s0[i]-'a'],lnow++;
            for(int j=0;j<ve[i].size();j++)
            {
                ques &qn=ve[i][j];
                ans[qn.id].first=qn.l;
                if(lnow<qn.le){continue;}
                int x=px;
                for(int k=21;k>=0;k--)if(s[fa[x][k]].len>=qn.le) x=fa[x][k];
                query(qn.l,qn.r,rt[x],1,m,ans[qn.id]);
            }
        }
    }
}sam;
int Iris()
{
    scanf("%s",s0+1),l0=strlen(s0+1);
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%s",s1+1),l1=strlen(s1+1);
        sam.fin=1;
        for(int j=1;j<=l1;j++) sam.ins(s1[j]-'a'),insert(i,rt[sam.fin],1,m);
    }
    scanf("%d",&qaq);
    for(int i=1,l,r,x,y;i<=qaq;i++)
    {
        scanf("%d%d%d%d",&l,&r,&x,&y);
        ve[y].push_back((ques){y,y-x+1,l,r,i});
    }
    sam.work();
    for(int i=1;i<=qaq;i++)
        printf("%d %d\n",ans[i].first,ans[i].second);
    return 0;
}
}
int main(){return RKK::Iris();}

原文地址:https://www.cnblogs.com/rikurika/p/12079264.html

时间: 2024-08-29 22:06:27

CF666E Forensic Examination(广义后缀自动机+线段树合并)的相关文章

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,这个点就是询问

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

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

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

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

【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

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

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