CF700E Cool Slogans 后缀自动机 + right集合线段树合并 + 树形DP

又是一道 SAM 神题.

Code:

#include<bits/stdc++.h>
#define maxn 400002
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
namespace tr
{
    #define lson t[x].l
    #define rson t[x].r
    #define mid ((l+r)>>1)
    int cnt;
    struct Node { int l,r; }t[maxn*20];
    void modify(int &x,int l,int r,int k)
    {
        if(!x) x=++cnt;
        if(l==r) return;
        if(k<=mid) modify(lson,l,mid,k);
        else modify(rson,mid+1,r,k);
    }
    int merge(int u,int v)
    {
        if(!u||!v) return u+v;
        int x=++cnt;
        lson=merge(t[u].l,t[v].l);
        rson=merge(t[u].r,t[v].r);
        return x;
    }
    int query(int x,int l,int r,int L,int R)
    {
        if(!x) return 0;
        if(l>=L&&r<=R) return 1;
        int tmp=0;
        if(L<=mid) tmp+=query(lson,l,mid,L,R);
        if(R>mid) tmp+=query(rson,mid+1,r,L,R);
        return tmp;
    }
};
namespace SAM
{
    char str[maxn];
    int last,tot,n;
    int trans[maxn][27],f[maxn],len[maxn],C[maxn],rk[maxn],top[maxn],dp[maxn],pos[maxn],rt[maxn];
    void init() { last=tot=1; }
    void extend(int c,int i)
    {
        int np=++tot,p=last;
        len[np]=len[p]+1,last=np;
        while(p&&!trans[p][c]) trans[p][c]=np,p=f[p];
        if(!p) f[np]=1;
        else
        {
            int q=trans[p][c];
            if(len[q]==len[p]+1) f[np]=q;
            else
            {
                int nq=++tot;
                len[nq]=len[p]+1;
                pos[nq]=pos[q];
                memcpy(trans[nq],trans[q],sizeof(trans[q]));
                f[nq]=f[q], f[np]=f[q]=nq;
                while(p&&trans[p][c]==q) trans[p][c]=nq,p=f[p];
            }
        }
        pos[np]=i;
        tr::modify(rt[last],1,n,i);
    }
    void prepare()
    {
        int i,j;
        init();
        scanf("%d%s",&n,str+1);
        for(i=1;i<=n;++i) extend(str[i]-‘a‘,i);
        for(i=1;i<=tot;++i) ++C[len[i]];
        for(i=1;i<=tot;++i) C[i]+=C[i-1];
        for(i=1;i<=tot;++i) rk[C[len[i]]--]=i;
        for(i=tot;i>1;--i)
        {
            int u=rk[i];
            rt[f[u]]=tr::merge(rt[f[u]],rt[u]);
        }
    }
    void calc()
    {
        int i,j,ans=1;
        for(i=2;i<=tot;++i)
        {
            int u=rk[i],ff=f[rk[i]];
            if(ff==1) { top[u]=u,dp[u]=1; continue; }
            if(tr::query(rt[top[ff]],1,n,pos[u]-len[u]+len[top[ff]],pos[u]-1))
                top[u]=u,dp[u]=dp[top[ff]]+1;
            else
                top[u]=top[ff];
            ans=max(ans,dp[u]);
        }
        printf("%d\n",ans);
    }
};
int main()
{
    //msetIO("input");
    SAM::prepare();
    SAM::calc();
    return 0;
}

  

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

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

CF700E Cool Slogans 后缀自动机 + right集合线段树合并 + 树形DP的相关文章

CF1037H Security 后缀自动机 + right集合线段树合并 + 贪心

后缀自动机 + 线段树合并的裸题. 我这种大菜逼都秒切的题目,一定是送分题. #include<bits/stdc++.h> #define setIO(s) freopen(s".in","r",stdin) #define maxn 220000 using namespace std; int n; int rt[maxn]; namespace tr { #define mid ((l+r)>>1) #define lson t[x]

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

[BJWC2018]Border 的四种求法(后缀自动机+链分治+线段树合并)

题目描述 给一个小写字母字符串 S ,q 次询问每次给出 l,r ,求 s[l..r] 的 Border . Border: 对于给定的串 s ,最大的 i 使得 s[1..i] = s[|s|-i+1..|s|], |s| 为 s 的长度. 题解 这题的描述很短,给人一种很可做的假象. 暴力1:每次对区间lr做一次KMP,求出border数组,复杂度nq. 暴力2:构建后缀自动机,用线段树合并维护出right集合考虑到两个串的最长后缀为他们在parent树上的LCA的len,所以我们可以在pa

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(后缀自动机+线段树合并)

给你一个串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[

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(广义后缀自动机+线段树合并)

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