Codeforces.700E.Cool Slogans(后缀自动机 线段树合并 DP)
\(Description\)
给定一个字符串\(s[1]\)。一个字符串序列\(s[\ ]\)满足\(s[i]\)至少在\(s[i-1]\)中出现过两次(\(i\geq 2\))。求最大的\(k\),满足存在\(s[1]\sim s[k]\)。
\(|s[1]|\leq2\times10^5\)。
\(Solution\)
一开始以为直接自底向上合并right,如果|right|>1就继续向上。这显然不对啊,这样出现次数>1不一定是在之前的子节点中出现次数>1。
如果串\(A\)出现在串\(B\)中两次,那么说明什么?用\(right(即endpos)\)表示一个串出现位置的最右端点集合,则\(right(A)\)在\(s[right(B)-len(A),right(B)]\)中至少出现过两次。
而且对于在原串中在任意位置出现的\(B\),\(A\)都满足上面的条件。
所以我们随便找一个\(pos[B]=某一个right(B)\),\(A\)若在\(B\)中出现至少两次则满足\(right(A)\)在\(s[pos[B]-len(B)+len(A),pos[B]]\)中至少出现了两次(\(s\)为原串)。
所有节点的\(right\)我们可以通过线段树合并全部得到。
\(parent\)树上的父节点能转移到子节点。可以看出从上到下的DP,如果满足条件则更新,用\(B\)(子节点)作为新的\(A\)(下次匹配作为父节点);不满足条件则保留之前的\(A\)(之前的父节点 更优)。
因为父节点\(A\)已经至少在子节点\(B\)中出现过一次了,所以只需要查\(s[pos[B]-len(B)+len(A),pos[B]-1]\)中是否存在\(right(A)\)(\(A\)的线段树中在该区间是否有值)。
(注意虽然\(A\)这里表示一个节点,似乎显然是用\(mnlen(A)\)更正确,而不是用\(A\)节点最长的串的长度\(len(A)\),但是...实际上是没问题的,因为既然同在\(A\)节点...就有些奇妙的性质。不放心就写\(mnlen\)吧)
还是有一种神奇hash做法。。见CF status。
顺便还有一种SA做法:

//249ms 131600KB
#include <cctype>
#include <cstdio>
#include <cstring>
#include <algorithm>
//#define gc() getchar()
#define MAXIN 300000
#define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
typedef long long LL;
const int N=2e5+5;
char IN[MAXIN],*SS=IN,*TT=IN;
inline int read()
{
int now=0;register char c=gc();
for(;!isdigit(c);c=gc());
for(;isdigit(c);now=now*10+c-'0',c=gc());
return now;
}
struct Segment_Tree
{
#define ls son[x][0]
#define rs son[x][1]
static const int S=N*2*25;//最好是4nlogn...
int tot,son[S][2];
void Insert(int &x,int l,int r,int p)
{
/*if(!x)*/ x=++tot;
if(l==r) return;
int m=l+r>>1;
if(p<=m) Insert(ls,l,m,p);
else Insert(rs,m+1,r,p);
}
bool Query(int x,int l,int r,int L,int R)
{
if(!x) return 0;
if(L<=l && r<=R) return 1;//有这个节点即可
int m=l+r>>1;
if(L<=m)
if(m<R) return Query(ls,l,m,L,R)||Query(rs,m+1,r,L,R);
else return Query(ls,l,m,L,R);
else return Query(rs,m+1,r,L,R);
}
int Merge(int x,int y)
{
if(!x||!y) return x|y;
int now=++tot;//!
son[now][0]=Merge(ls,son[y][0]), son[now][1]=Merge(rs,son[y][1]);
return now;
}
};
struct Suffix_Automaton
{
static const int S=N<<1;
int n,tot,las,len[S],pos[S],son[S][26],fa[S],tm[S],A[S],root[S],f[S],top[S];
Segment_Tree T;
Suffix_Automaton() {tot=las=1;}
void Insert(int c,int id)
{
int np=++tot,p=las;
len[las=np]=len[p]+1, pos[np]=id;
for(; p&&!son[p][c]; p=fa[p]) son[p][c]=np;
if(!p) fa[np]=1;
else
{
int q=son[p][c];
if(len[q]==len[p]+1) fa[np]=q;
else
{
int nq=++tot;
len[nq]=len[p]+1, pos[nq]=pos[q]/*!*/;
memcpy(son[nq],son[q],sizeof son[q]);
fa[nq]=fa[q], fa[q]=fa[np]=nq;
for(; son[p][c]==q; p=fa[p]) son[p][c]=nq;
}
}
}
void Solve()
{
n=read(); register char c=gc(); for(;!isalpha(c);c=gc());
Insert(c-'a',1),T.Insert(root[las],1,n,1);
for(int i=2; i<=n; ++i) Insert(gc()-'a',i),T.Insert(root[las],1,n,i);
for(int i=1; i<=tot; ++i) ++tm[len[i]];
for(int i=1; i<=n; ++i) tm[i]+=tm[i-1];
for(int i=1; i<=tot; ++i) A[tm[len[i]]--]=i;
for(int i=tot,x=A[i]; i>1; x=A[--i]) root[fa[x]]=T.Merge(root[fa[x]],root[x]);
int ans=1;
for(int i=2,x=A[i],tp; i<=tot; x=A[++i])
{
if(fa[x]==1) {f[x]=1, top[x]=x; continue;}
tp=top[fa[x]];
if(T.Query(root[tp],1,n,pos[x]-len[x]+len[tp],pos[x]-1))//or pos[x]-len[x]+len[fa[tp]]+1
f[x]=f[tp]+1, top[x]=x, ans=std::max(ans,f[x]);
else f[x]=f[tp], top[x]=tp;
}
printf("%d\n",ans);
}
}sam;
int main()
{
sam.Solve();
return 0;
}
Codeforces.700E.Cool Slogans(后缀自动机 线段树合并 DP)的更多相关文章
- 【CF700E】Cool Slogans 后缀自动机+线段树合并
[CF700E]Cool Slogans 题意:给你一个字符串S,求一个最长的字符串序列$s_1,s_2,...,s_k$,满足$\forall s_i$是S的子串,且$s_i$在$s_{i-1}$里 ...
- Codeforces 700E. Cool Slogans 字符串,SAM,线段树合并,动态规划
原文链接https://www.cnblogs.com/zhouzhendong/p/CF700E.html 题解 首先建个SAM. 一个结论:对于parent树上任意一个点x,以及它所代表的子树内任 ...
- BZOJ3413: 匹配(后缀自动机 线段树合并)
题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并... 首先可以转化一下模型(想不到qwq):问题可以转化为统计\(B\)中每个前缀在\(A\)中出现的次数.(画一画就出来了) 然后直 ...
- cf666E. Forensic Examination(广义后缀自动机 线段树合并)
题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并 首先对所有的\(t_i\)建个广义后缀自动机,这样可以得到所有子串信息. 考虑把询问离线,然后把\(S\)拿到自动机上跑,同时维护一下 ...
- [Luogu5161]WD与数列(后缀数组/后缀自动机+线段树合并)
https://blog.csdn.net/WAautomaton/article/details/85057257 解法一:后缀数组 显然将原数组差分后答案就是所有不相交不相邻重复子串个数+n*(n ...
- 模板—字符串—后缀自动机(后缀自动机+线段树合并求right集合)
模板—字符串—后缀自动机(后缀自动机+线段树合并求right集合) Code: #include <bits/stdc++.h> using namespace std; #define ...
- 【BZOJ4556】[TJOI2016&HEOI2016] 字符串(后缀自动机+线段树合并+二分)
点此看题面 大致题意: 给你一个字符串\(s\),每次问你一个子串\(s[a..b]\)的所有子串和\(s[c..d]\)的最长公共前缀. 二分 首先我们可以发现一个简单性质,即要求最长公共前缀,则我 ...
- bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并)
bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并) bzoj Luogu 给出一个字符串 $ S $ 及 $ q $ 次询问,每次询问一个字符串 $ T $ ...
- Codeforces 666E Forensic Examination(广义后缀自动机+线段树合并)
将所有串(包括S)放一块建SAM.对于询问,倍增定位出该子串所在节点,然后要查询的就是该子串在区间内的哪个字符串出现最多.可以线段树合并求出该节点在每个字符串中的出现次数. #include<b ...
随机推荐
- jQuery-contextMenu使用教程
jQuery-contextMenu使用教程 效果如下图所示.在[右击菜单]处右击,会出现下面的效果. 添加引用 <script src="jQuery-contextMenu-mas ...
- FarBox--另类有趣的网站服务【转】
FarBox--另类有趣的网站服务 转自:http://mosir.org/html/y2012/the-interesting-web-service-serve-by-FarBox.html 作者 ...
- rsync同步(winxdows到linux/linux到linxu同步)
1.什么是rsync? -rsync是类unix系统下的数据镜像备份工具——remote sync.一款快速增量备份工具 Remote Sync,远程同步 支持本地复制,或者与其他SSH.rsync主 ...
- redis学习笔记(面试题)
1. 什么是redis Redis是一个数据库,他和我们传统的oracle数据库差别是它是基于内存的数据库:因为是基于内存,所以效率就高,在某些场景下就可以对我们传统的关系型数据库做一个补充 2. r ...
- 【bzoj1901】dynamic ranking(带修改主席树)
传送门(权限) 传送门(非权限) 花了一晚上总算把代码调好了……才知道待修改主席树怎么操作…… 然而还是一知半解orz…… 先说说我的理解吧 我们一般建主席树的时候都是直接在序列上建的 但是如果有修改 ...
- 转载:小结(1.7)《深入理解Nginx》(陶辉)
原文:https://book.2cto.com/201304/19622.html 本章介绍了Nginx的特点以及在什么场景下需要使用Nginx,同时介绍了如何获取Nginx以及如何配置.编译.安装 ...
- navicat连接sqlserver数据库提示:未发现数据源名称并且未指定默认驱动程序
原因是navicat没有安装sqlserver驱动,就在navicat安装目录下,找到双击安装即可:
- python接口自动化测试四:代码发送HTTPS请求
HTTPS: get: url = 'https://www.juhe.cn/docs/api/id/39' r = requests.get(url) # ...
- python 全栈开发,Day35(TCP协议 粘包现象 和解决方案)
一.TCP协议 粘包现象 和解决方案 黏包现象让我们基于tcp先制作一个远程执行命令的程序(命令ls -l ; lllllll ; pwd)执行远程命令的模块 需要用到模块subprocess sub ...
- oracle表分区创建
一.什么是分区表表分区有以下优点: 1.数据查询:数据被存储到多个文件上,减少了I/O负载,查询速度提高. 2.数据修剪:保存历史数据非常的理想. 3.备份:将大表的数据分成多个文件,方便备份和恢复. ...