[CF666E]Forensic Examination

luogu

题意

给你一个串\(S\)以及一个字符串数组\(T[1..m]\),\(q\)次询问,每次问\(S\)的子串\(S[p_l..p_r]\)在\(T[l..r]\)中的哪个串里的出现次数最多,并输出出现次数。
如有多解输出最靠前的那一个。
\(|S|\le5*10^5,\sum|T|\le5*10^4,q\le5*10^5\)

sol

首先肯定是对\(T[1..m]\)这个字符串数组构出广义\(SAM\)。
考虑串\(S\)在这个\(SAM\)上的匹配,是\(O(|S|)\)的。显然不能对于每组询问都暴力匹配一遍。
把所有询问离线,按照询问的\(p_r\)挂链,当原串在\(SAM\)上匹配到\(p_r\)位置的时候,这个子串\(S[p_l..p_r]\)一定会是当前状态的一个祖先(当然也有可能这个子串根本就没在\(SAM\)里面出现,这个特判掉就好了)。
找祖先显然可以倍增一下,跳到最上方的满足\(len_u\ge q_r-q_l+1\)的点\(u\)就好了。
我们现在已经找到了这个对应状态,现在需要知道这个状态分别在哪些串里面出现了多少次。对每个节点开一棵线段树,线段树以字符串数组的下表为下标\((1..m)\),值表示当前这个状态在字符串\(T[i]\)中的出现次数。需要支持查询区间最小值以及位置。
一开始线段树是只有底层状态的,对于更上层的状态如何处理?
线段树合并即可。

code

挂链的东西太多了所以直接开了int nxt[3][N],head[3][N];
\(0\)是后缀树上对父亲挂的链,\(1\)是询问对右端点挂的链,\(2\)是询问对倍增跳到的状态节点挂的链。

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi()
{
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
const int N = 5e5+5;
struct data{
    int x,y;
    bool operator < (const data &b) const
        {return x<b.x||x==b.x&&y>b.y;}
}ans[N];
struct seg{int ls,rs;data v;}t[N*20];
struct node{int l,r,pl,pr;}q[N];
int n,m,Q,last=1,tot=1,tr[N][26],fa[22][N],len[N],rt[N],Node;
int nxt[3][N],head[3][N];
char S[N],T[N];
void extend(int c)
{
    int v=last,u=++tot;last=u;
    len[u]=len[v]+1;
    while (v&&!tr[v][c]) tr[v][c]=u,v=fa[0][v];
    if (!v) fa[0][u]=1;
    else{
        int x=tr[v][c];
        if (len[x]==len[v]+1) fa[0][u]=x;
        else{
            int y=++tot;
            memcpy(tr[y],tr[x],sizeof(tr[y]));
            fa[0][y]=fa[0][x];fa[0][x]=fa[0][u]=y;len[y]=len[v]+1;
            while (v&&tr[v][c]==x) tr[v][c]=y,v=fa[0][v];
        }
    }
}
void modify(int &x,int l,int r,int p)
{
    if (!x) x=++Node;
    if (l==r) {t[x].v.x++;t[x].v.y=l;return;}
    int mid=l+r>>1;
    if (p<=mid) modify(t[x].ls,l,mid,p);
    else modify(t[x].rs,mid+1,r,p);
    t[x].v=max(t[t[x].ls].v,t[t[x].rs].v);
}
void merge(int &x,int y)
{
    if (!x||!y) {x=x|y;return;}
    if (!t[x].ls&&!t[x].rs) {t[x].v.x+=t[y].v.x;return;}//叶子节点,直接累加
    merge(t[x].ls,t[y].ls);merge(t[x].rs,t[y].rs);
    t[x].v=max(t[t[x].ls].v,t[t[x].rs].v);
}
data query(int x,int l,int r,int ql,int qr)
{
    if (l>=ql&&r<=qr) return t[x].v;
    int mid=l+r>>1;
    if (qr<=mid) return query(t[x].ls,l,mid,ql,qr);
    if (ql>mid) return query(t[x].rs,mid+1,r,ql,qr);
    return max(query(t[x].ls,l,mid,ql,qr),query(t[x].rs,mid+1,r,ql,qr));
}
void dfs(int u)
{
    for (int i=head[0][u];i;i=nxt[0][i])
        dfs(i),merge(rt[u],rt[i]);
    for (int i=head[2][u];i;i=nxt[2][i])
        ans[i]=query(rt[u],1,m,q[i].l,q[i].r);
}
int main()
{
    scanf("%s",S+1);n=strlen(S+1);
    m=gi();
    for (int i=1;i<=m;++i)
    {
        scanf("%s",T+1);int len=strlen(T+1);last=1;
        for (int j=1;j<=len;++j) extend(T[j]-'a'),modify(rt[last],1,m,i);
    }
    Q=gi();
    for (int i=1;i<=Q;++i)
    {
        q[i]=(node){gi(),gi(),gi(),gi()};
        nxt[1][i]=head[1][q[i].pr],head[1][q[i].pr]=i;
    }
    for (int i=2;i<=tot;++i)
        nxt[0][i]=head[0][fa[0][i]],head[0][fa[0][i]]=i;
    for (int j=1;j<22;++j)
        for (int i=2;i<=tot;++i)
            fa[j][i]=fa[j-1][fa[j-1][i]];
    for (int i=1,now=1,cnt=0;i<=n;++i)
    {
        while (now&&!tr[now][S[i]-'a']) now=fa[0][now],cnt=len[now];
        if (!now) {now=1;cnt=0;continue;}
        now=tr[now][S[i]-'a'];++cnt;
        for (int j=head[1][i];j;j=nxt[1][j])
        {
            int u=now;if (cnt<q[j].pr-q[j].pl+1) continue;
            for (int k=21;~k;--k)
                if (len[fa[k][u]]>=q[j].pr-q[j].pl+1)
                    u=fa[k][u];
            nxt[2][j]=head[2][u];head[2][u]=j;
        }
    }
    dfs(1);
    for (int i=1;i<=Q;++i)
    {
        if (!ans[i].x) ans[i].y=q[i].l;
        printf("%d %d\n",ans[i].y,ans[i].x);
    }
    return 0;
}

原文地址:https://www.cnblogs.com/zhoushuyu/p/8798559.html

时间: 2024-10-12 05:59:14

[CF666E]Forensic Examination的相关文章

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

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

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

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 某个子树部分都是包含他自己的串,所以线段树合并一下就变成了子树数颜色以及找到

CF666E 【Forensic Examination】

题目 每天一道\(SAM\)真是非常开心 一看就是广义\(SAM\)+线段树合并了 我们存好\(S\)串每一个前缀的终点,之后在\(parent\)树上倍增找到表示\(S[l,r]\)这个子串的节点,我们用线段树合并维护好\(endpos\)集合,查一个区间最大值就好了 代码 #include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #define maxn 20000

【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

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

[CF 666E] Forensic Examination

Description 传送门 Solution Code #include <cstdio> #include <vector> #include <cstring> #include <algorithm> const int N = 500005, M = 9500005; struct Edge { int v, nxt; } e[N]; struct Node { int l, r, x, y, id; bool operator < (co