Cool Slogans(后缀自动机+线段树+dp)

传送门:https://www.luogu.org/problemnew/show/CF700

先手动模拟一下:

原串:abracadabra

s数组依次是:abracadabra,abra,a

可以发现,每一步我们找最长的在上一个串中出现两次的子串,即可得到最优解

很容易想到dp:

定义两个数组:
dp[i]:使用节点i最长的那个字符串的答案
mx[i]:节点i最长的那个字符串对应的节点

设A是B的子串
if(A在B中出现两次) dp[B]=dp[A]+1,mx[B]=B;
else dp[B]=dp[A],mx[B]=mx[A];

接下来只需要检查A在B中出现两次就行了:

于是考虑一下维护每一个点的endpos集合,这个只要用线段树就行了。

如果A在B中出现了两次,那么A的endpos集合在[pos[B]−len[B]+len[A],pos[B]]中出现了至少两次(其中pos[B]表示B的任意一个endpos)。

所以可以在parent树上dp,由父亲节点转移到儿子节点。

令A=mx[fa[x]],B=x,因为parent树上父亲是儿子的严格后缀,所以必然在儿子里出现了一次,那么只要考虑endpos[A]中是否有元素在[pos[B]−len[B]+len[A],pos[B]−1]中就行了

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=400005;
struct node{
    int ls,rs;
}st[maxn*50];
int sz,rt[maxn],dp[maxn],mx[maxn];
int tot=1,lst=1;
int n,ch[maxn][26],len[maxn],fa[maxn],pos[maxn];
int tax[maxn],id[maxn];
char s[maxn];
void add(int c,int i){
    int p=lst;
    int np=lst=++tot;
    len[np]=len[p]+1;pos[np]=i;
    for(;p&&!ch[p][c];p=fa[p])
        ch[p][c]=np;
    if(!p) fa[np]=1;
    else{
        int q=ch[p][c];
        if(len[q]==len[p]+1) fa[np]=q;
        else{
            int nq=++tot;
            memcpy(ch[nq],ch[q],sizeof ch[q]);
            pos[nq]=i;
            len[nq]=len[p]+1;
            fa[nq]=fa[q];fa[q]=fa[np]=nq;
            for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
        }
    }
}
void insert(int &u,int l,int r,int x){
    if (!u) u=++sz;
    if (l==r) return;
    int mid=(l+r)/2;
    if (x<=mid) insert(st[u].ls,l,mid,x);
    else insert(st[u].rs,mid+1,r,x);
}
int merge(int x,int y){
    if (!x||!y) return x+y;
    int u=++sz;
    st[u].ls=merge(st[x].ls,st[y].ls);
    st[u].rs=merge(st[x].rs,st[y].rs);
    return u;
}
int query(int u,int l,int r,int x,int y){
    if(!u) return 0;
    if(l==x&&r==y) return 1;
    int mid=(l+r)/2;
    if(x<=mid&&query(st[u].ls,l,mid,x,min(y,mid))) return 1;
    if(y>mid&&query(st[u].rs,mid+1,r,max(x,mid+1),y)) return 1;
    return 0;
}
void build(){
    for(int i=1;i<=tot;i++) tax[len[i]]++;
    for(int i=1;i<=tot;i++) tax[i]+=tax[i-1];
    for(int i=1;i<=tot;i++) id[tax[len[i]]--]=i;
    for(int i=tot;i>=2;i--){
        int x=id[i];
        insert(rt[x],1,n,pos[x]);//endpos{x}中插入pos[x]
        rt[fa[x]]=merge(rt[fa[x]],rt[x]);//endpos{fa[x]}等于其子节点endpos{}的集合
    }
}
int main(){
    scanf("%d",&n);
    scanf("%s",s+1);
    for(int i=1;i<=n;i++) add(s[i]-‘a‘,i);
    build();
    int ans=1;
    for (int i=2;i<=tot;i++){
        int x=id[i];
        if(fa[x]==1){dp[x]=1;mx[x]=x;continue;}
        int flag=query(rt[mx[fa[x]]],1,n,pos[x]-len[x]+len[mx[fa[x]]],pos[x]-1);
        if(flag) dp[x]=dp[fa[x]]+1,mx[x]=x;
        else dp[x]=dp[fa[x]],mx[x]=mx[fa[x]];
        ans=max(ans,dp[x]);
    }
    printf("%d",ans);
    return 0;
}

原文地址:https://www.cnblogs.com/HarryPotter-fan/p/10958857.html

时间: 2024-10-01 23:55:17

Cool Slogans(后缀自动机+线段树+dp)的相关文章

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

[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

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[

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

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

识别子串 (string)——后缀自动机+线段树

题目 [题目描述] 一般地,对于一个字符串 S,和 S 中第 $ i $ 个字符 x,定义子串 $ T=S(i.j) $ 为一个关于 x 的识别子申,当且仅当: 1.$ i \leq x \leq j $ 2.T 在 S 巾只出现一次 比如,对于 banana 的第 $ 5 $ 个字符,“nana”, “anan”,“anana”, “nan”,“banan” 和“banana”都是关于它的识别子串. 说你写一个程序,计算出对对于一个字符串 S,关于 S 的每一位的最短识别子串的长度. [输入格

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的

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