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;
    int ch[N<<1][10],pre[N<<1],len[N<<1];
    void init() { last=tot=1;}
    void extend(int c)
    {
        int np=++tot,p=last;
        len[np]=len[p]+1,last=np;
        for(;p&&!ch[p][c];p=pre[p])  ch[p][c]=np;
        if(!p) pre[np]=1;
        else
        {
            int q=ch[p][c];
            if(len[q]==len[p]+1)     pre[np]=q;
            else
            {
                int nq=++tot;
                len[nq]=len[p]+1;
                memcpy(ch[nq],ch[q],sizeof(ch[q]));
                pre[nq]=pre[q],pre[q]=pre[np]=nq;
                for(;p&&ch[p][c]==q;p=pre[p])    ch[p][c]=nq;
            }
        }
    }
}s1,s2;
int tot;
char str[N];
int lson[N*30],rson[N*30],sum[N*30],rt[N<<1],tax[N<<1],rk[N<<1];
int newnode() { return ++ tot; }
void update(int &x,int l,int r,int p)
{
    if(!x) x=newnode();
    ++sum[x];
    if(l==r) return ;
    int mid=(l+r)>>1;
    if(p<=mid)   update(lson[x],l,mid,p);
    else    update(rson[x],mid+1,r,p);
}
int query(int x,int l,int r,int L,int R)
{
    if(!x) return 0;
    if(l>=L&&r<=R) { return sum[x]; }
    int mid=(l+r)>>1, re=0;
    if(L<=mid)     re+=query(lson[x],l,mid,L,R);
    if(R>mid)      re+=query(rson[x],mid+1,r,L,R);
    return re;
}
int merge(int x,int y)
{
    if(!x||!y)   return x+y;
    int now=newnode();
    sum[now]=sum[x]+sum[y];
    lson[now]=merge(lson[x],lson[y]);
    rson[now]=merge(rson[x],rson[y]);
    return now;
}
int getmin(int x,int l,int r)
{
    if(l==r)     return l;
    int mid=(l+r)>>1;
    if(sum[lson[x]])    return getmin(lson[x],l,mid);
    else return getmin(rson[x],mid+1,r);
}
int main()
{
    // setIO("input");
    s1.init();
    s2.init();
    int n,i,j,m;
    scanf("%d%s",&n,str+1);
    for(i=1;i<=n;++i)   s1.extend(str[i]-‘0‘);
    for(i=n;i>=1;--i)
    {
        s2.extend(str[i]-‘0‘);
        int lst=s2.last;
        update(rt[lst],1,n+1,i);
    }
    // 线段树合并
    for(i=1;i<=s2.tot;++i)   ++tax[s2.len[i]];
    for(i=1;i<=s2.tot;++i)   tax[i]+=tax[i-1];
    for(i=1;i<=s2.tot;++i)   rk[tax[s2.len[i]]--]=i;
    for(i=s2.tot;i>1;--i)
    {
        int u=rk[i];
        int ff=s2.pre[u];
        rt[ff]=merge(rt[ff],rt[u]);
    }
    scanf("%d",&m);
    for(i=1;i<=m;++i)
    {
        scanf("%s",str+1);
        int length=strlen(str+1),pp=1,len=0,mn=n+1;
        for(j=1;j<=length;++j)
        {
            if(s1.ch[pp][str[j]-‘0‘])  pp=s1.ch[pp][str[j]-‘0‘],++len;
            else break;
        }
        for(pp=1,j=len;j>=1;--j)  pp=s2.ch[pp][str[j]-‘0‘];
        if(len==length)    mn=getmin(rt[pp],1,n+1);
        LL ans=0ll;
        while(pp!=1)
        {
            LL tmp=query(rt[pp],1,n+1,1,mn);
            ans+=tmp*(min(len,s2.len[pp])-s2.len[s2.pre[pp]]);
            pp=s2.pre[pp];
        }
        ans+=1ll*(mn-1ll);
        printf("%lld\n",ans);
    }
    return 0;
}

  

原文地址:https://www.cnblogs.com/guangheli/p/11779123.html

时间: 2024-10-12 11:38:02

bzoj 3413: 匹配 后缀自动机+线段树合并的相关文章

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

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 首先把后缀

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 $ 的出现次数. 很明显用线段树

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

BZOJ 1396&amp;&amp;2865 识别子串[后缀自动机 线段树]

Description 在这个问题中,给定一个字符串S,与一个整数K,定义S的子串T=S(i, j)是关于第K位的识别子串,满足以下两个条件: 1.i≤K≤j. 2.子串T只在S中出现过一次. 例如,S="banana",K=5,则关于第K位的识别子串有"nana","anan","anana","nan","banan"和"banana". 现在,给定S,求对于S的

[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树的每个节点子树中叶子的数量就是该节点