CF700E:Cool Slogans(后缀自动机,线段树合并)

Description

给你一个字符串,如果一个串包含两个不重叠的相同子串,那么这个串的价值就是子串的价值+1。问你给定字符串的最大价值子串的价值。

Input

第一行读入字符串长度$n$,第二行是字符串。

Output

一行答案。

Sample Input1

3
abc

Sample Output1

1

Sample Input2

5
ddddd

Sample Output2

5

Sample Input3

11
abracadabra

Sample Output3

3

Solution

首先把后缀树建立出来,然后从下往上线段树合并一下$endpos$。

设$f[i]$表示从后缀树的根$DP$到了$i$节点的最大价值,$top[i]$表示$i$节点是从哪个节点转移来的。

如果父亲代表的字符串在当前节点代表的字符串中出现了两次及以上,那么就$f[x]=f[fa]+1,top[x]=x$

否则$f[x]=f[fa],top[x]=top[fa]$

父亲代表的字符串在当前节点代表的字符串中出现的次数可以直接根据$SAM$的$step$数组和线段树合并出的$endpos$什么的直接判断一下。

Code

  1 #include<iostream>
  2 #include<cstring>
  3 #include<cstdio>
  4 #define N (800009)
  5 using namespace std;
  6
  7 struct Sgt{int ls,rs,val;}Segt[N<<5];
  8 struct Edge{int to,next;}edge[N<<1];
  9 int n,ans,sgt_num,Root[N],f[N],top[N];
 10 int head[N],num_edge;
 11 int last=1,p,q,np,nq,cnt=1;
 12 int fa[N],son[N][26],step[N],pos[N];
 13 char s[N];
 14
 15 void add(int u,int v)
 16 {
 17     edge[++num_edge].to=v;
 18     edge[num_edge].next=head[u];
 19     head[u]=num_edge;
 20 }
 21
 22 void Insert(int x,int r)
 23 {
 24     p=last; np=last=++cnt;
 25     step[np]=step[p]+1; pos[np]=r;
 26     while (p && !son[p][x]) son[p][x]=np, p=fa[p];
 27     if (!p) fa[np]=1;
 28     else
 29     {
 30         q=son[p][x];
 31         if (step[q]==step[p]+1) fa[np]=q;
 32         else
 33         {
 34             nq=++cnt; step[nq]=step[p]+1; pos[nq]=r;
 35             memcpy(son[nq],son[q],sizeof(son[q]));
 36             fa[nq]=fa[q]; fa[q]=fa[np]=nq;
 37             while (son[p][x]==q) son[p][x]=nq, p=fa[p];
 38         }
 39     }
 40 }
 41
 42 void Update(int &now,int l,int r,int x)
 43 {
 44     if (!now) now=++sgt_num;
 45     Segt[now].val++;
 46     if (l==r) return;
 47     int mid=(l+r)>>1;
 48     if (x<=mid) Update(Segt[now].ls,l,mid,x);
 49     else Update(Segt[now].rs,mid+1,r,x);
 50 }
 51
 52 int Merge(int x,int y)
 53 {
 54     if (!x || !y) return x|y;
 55     int now=++sgt_num;
 56     Segt[now].ls=Merge(Segt[x].ls,Segt[y].ls);
 57     Segt[now].rs=Merge(Segt[x].rs,Segt[y].rs);
 58     Segt[now].val=Segt[x].val+Segt[y].val;
 59     return now;
 60 }
 61
 62 int Query(int now,int l,int r,int l1,int r1)
 63 {
 64     if (!now) return 0;
 65     if (l>r1 || r<l1) return 0;
 66     if (l1<=l && r<=r1) return Segt[now].val;
 67     int mid=(l+r)>>1;
 68     return Query(Segt[now].ls,l,mid,l1,r1)+Query(Segt[now].rs,mid+1,r,l1,r1);
 69 }
 70
 71 void DFS(int x)
 72 {
 73     if (pos[x]) Update(Root[x],1,n,pos[x]);
 74     for (int i=head[x]; i; i=edge[i].next)
 75     {
 76         DFS(edge[i].to);
 77         Root[x]=Merge(Root[x],Root[edge[i].to]);
 78     }
 79 }
 80
 81 void DP(int x)
 82 {
 83     for (int i=head[x]; i; i=edge[i].next)
 84     {
 85         int y=edge[i].to;
 86         if (x==1) f[y]=1, top[y]=y;
 87         else if (Query(Root[top[x]],1,n,pos[y]-step[y]+step[top[x]],pos[y]-1))
 88             f[y]=f[x]+1, top[y]=y;
 89         else f[y]=f[x], top[y]=top[x];
 90         DP(edge[i].to);
 91         ans=max(ans,f[edge[i].to]);
 92     }
 93 }
 94
 95 int main()
 96 {
 97     scanf("%d%s",&n,s);
 98     for (int i=0; i<n; ++i) Insert(s[i]-‘a‘,i+1);
 99     for (int i=2; i<=cnt; ++i) add(fa[i],i);
100     DFS(1); DP(1);
101     printf("%d\n",ans);
102 }

原文地址:https://www.cnblogs.com/refun/p/10295294.html

时间: 2024-12-08 15:23:13

CF700E:Cool Slogans(后缀自动机,线段树合并)的相关文章

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

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

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]=

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

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