题意:定义长度为$k$的journey为一个字符串序列$t_{1\cdots k}$,对$\forall i\gt1$满足$t_i$是$t_{i-1}$的严格子串,定义字符串$s$上的journey为$t_{1\cdots k}$在$s$中依次不重叠出现的journey,给定$s$,求$s$上的最长journey

首先,如果我们能找到长度为$k$的journey,那么一定存在一个长度为$k$的journey满足$|t_i|=|t_{i-1}|-1$,这个直接在原journey上删一些字符即可

然后有一条性质:如果一个长度为$k$的journey满足$t_1$从$s_i$开始,那么对$\forall j\leq k$,存在长度为$j$的journey满足$t_1$从$s_i$开始,这个同样可以在原journey中删字符得到

所以考虑DP:设$f_i$表示最长的journey使得$t_1$从$s_i$开始,枚举$t_1=s_{i\cdots i+k-1}$,得到转移$f_i=\max\left\{k|\exists j\geq i+k,f_j\geq k-1,s_{j\cdots j+k-2}\subset s_{i\cdots i+k-1}\right\}$

如果知道怎么解决后面的限制,那么我们可以用二分来求$f_i$,需要$O(n\log n)$次check,但因为$f_{i+1}\geq f_i-1$(如果$f_i$表示的journey满足$t_2$在$t_1$的开始匹配位置是$s_i$,那么$f_{i+1}$就可能$-1$),所以我们可以从$f_{i+1}+1$开始往下check,总共调用$O(n)$次check

现在来看怎么check,字符串的条件是说$s_{j\cdots j+k-2}=s_{i\cdots i+k-2}$或$s_{i+1\cdots i+k-1}$,而子串在后缀数组上对应着一个区间,所以我们可以把$f_i$赋到$rank_i$上,在线段树上查询区间最大值即可,还有一个条件$j\geq i+k$,但很容易发现满足条件的$j$其实是随着$i$减小而单调不增的($i$不变时$k$在不断减小,$i$减少$1$的同时$k$只能增加$1$),所以在线段树上动态插入即可,单次check的时间复杂度为$O(\log n)$

总时间复杂度为$O(n\log n)$,是很优美的复杂度

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
char s[500010];
int rk[1000010],sa[500010],c[500010],h[500010],mn[500010][19],lg[500010],n;
struct pr{
	int c[2],i;
	pr(int a=0,int b=0,int d=0){c[0]=a;c[1]=b;i=d;}
}p[500010],q[500010];
bool operator!=(pr a,pr b){return a.c[0]!=b.c[0]||a.c[1]!=b.c[1];}
void sort(int f){
	int i,m;
	memset(c,0,sizeof(c));
	m=0;
	for(i=1;i<=n;i++){
		c[p[i].c[f]]++;
		m=max(m,p[i].c[f]);
	}
	for(i=1;i<=m;i++)c[i]+=c[i-1];
	for(i=n;i>0;i--)q[c[p[i].c[f]]--]=p[i];
	memcpy(p,q,sizeof(q));
}
void suf(){
	int i,j,l,m;
	for(i=1;i<=n;i++)rk[i]=s[i]-'a'+1;
	for(l=1;l<=n;l<<=1){
		for(i=1;i<=n;i++)p[i]=pr(rk[i],rk[i+l],i);
		sort(1);
		sort(0);
		for(i=1,m=0;i<=n;i++){
			if(p[i]!=p[i-1])m++;
			rk[p[i].i]=m;
		}
	}
	for(i=1;i<=n;i++)sa[rk[i]]=i;
	for(i=1,l=0;i<=n;i++){
		if(l)l--;
		while(s[i+l]==s[sa[rk[i]-1]+l])l++;
		h[rk[i]]=l;
	}
	for(i=1;i<=n;i++)mn[i][0]=h[i];
	for(j=1;j<19;j++){
		for(i=1;i+(1<<j)-1<=n;i++)mn[i][j]=min(mn[i][j-1],mn[i+(1<<(j-1))][j-1]);
	}
	for(i=2;i<=n;i++)lg[i]=lg[i>>1]+1;
}
int qmin(int l,int r){
	int k=lg[r-l+1];
	return min(mn[l][k],mn[r-(1<<k)+1][k]);
}
int mx[2097152],M;
void modify(int x,int v){
	if(!x)return;
	x+=M;
	mx[x]=max(mx[x],v);
	for(x>>=1;x;x>>=1)mx[x]=max(mx[x<<1],mx[x<<1|1]);
}
int query(int s,int t){
	int res=-1;
	for(s+=M-1,t+=M+1;s^t^1;s>>=1,t>>=1){
		if(~s&1)res=max(res,mx[s^1]);
		if(t&1)res=max(res,mx[t^1]);
	}
	return res;
}
int submax(int s,int k){
	int l,r,mid,ans,lp,rp;
	if(k==0)return mx[1];
	ans=s;
	l=1;
	r=s-1;
	while(l<=r){
		mid=(l+r)>>1;
		if(qmin(mid+1,s)>=k){
			ans=mid;
			r=mid-1;
		}else
			l=mid+1;
	}
	lp=ans;
	ans=s;
	l=s+1;
	r=n;
	while(l<=r){
		mid=(l+r)>>1;
		if(qmin(s+1,mid)>=k){
			ans=mid;
			l=mid+1;
		}else
			r=mid-1;
	}
	rp=ans;
	return query(lp,rp);
}
int f[500010];
int main(){
	int i,j,ans;
	scanf("%d%s",&n,s+1);
	suf();
	memset(mx,-1,sizeof(mx));
	for(M=1;M<=n+2;M<<=1);
	modify(n+1,0);
	ans=0;
	for(i=n;i>0;i--){
		for(j=f[i+1]+1;;j--){
			modify(rk[i+j],f[i+j]);
			if(max(submax(rk[i],j-1),submax(rk[i+1],j-1))>=j-1)break;
		}
		f[i]=j;
		ans=max(ans,f[i]);
	}
	printf("%d",ans);
}

[CF1063F]String Journey的更多相关文章

  1. [CF1063F]String Journey[后缀数组+线段树]

    题意 在 \(S\) 中找出 \(t\) 个子串满足 \(t_{i+1}\) 是 \(t_{i}\) 的子串,要让 \(t\) 最大. \(|S| \leq 5\times 10^5\). 分析 定义 ...

  2. 解题:CF1063F String Journey

    题面 分析性质以进行DP 性质1:一定有一个最优解通过每次删除第一个或最后一个字符达到 这个脑补一下就能证明了 那么我们设$dp[i]$表示后缀$[i,n]$选出一个前缀所能达到的最大长度,从右往左D ...

  3. CF1063F. String Journey(后缀数组+线段树)

    题目链接 https://codeforces.com/contest/1063/problem/F 题解 虽然本题有时间复杂度较高但非常好写的做法...... 首先,若答案为 \(k\),则一定存在 ...

  4. CF1063F String Journey DP、SAM、线段树

    传送门 为了方便把串反过来,条件变为\(t_i\)是\(t_{i+1}\)的真子串,答案显然不变. 一件重要的事情是必定存在一种最优解,字符串序列\(\{t\}\)满足\(|t_i| = i\). 考 ...

  5. 【CF1063F】String Journey 哈希

    题目大意 给你一个字符串 \(s\),让你找出最大的 \(k\),满足:能从 \(s\) 中选出 \(k\) 个不重叠的字符串 \(t_1,t_2,\ldots,t_k\),且 \(\forall i ...

  6. Codeforces 1063F - String Journey(后缀数组+线段树+dp)

    Codeforces 题面传送门 & 洛谷题面传送门 神仙题,做了我整整 2.5h,写篇题解纪念下逝去的中午 后排膜拜 1 年前就独立切掉此题的 ymx,我在 2021 年的第 5270 个小 ...

  7. CF721C. Journey[DP DAG]

    C. Journey time limit per test 3 seconds memory limit per test 256 megabytes input standard input ou ...

  8. A Knight's Journey 分类: POJ 搜索 2015-08-08 07:32 2人阅读 评论(0) 收藏

    A Knight's Journey Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 35564 Accepted: 12119 ...

  9. 【推公式】UVa 10995 - Educational Journey

    1A~,但后来看人家的代码好像又写臭了,T^T... Problem A: Educational journey The University of Calgary team qualified f ...

随机推荐

  1. charles https抓包

    1. 配置 Charles 根证书 首先打开 Charles: Charles 启动界面 主界面 然后如下图操作:   之后会弹出钥匙串,如果不弹出,请自行打开钥匙串,如下图: 钥匙串 系统默认是不信 ...

  2. Java中class的初始化顺序

     由于Java 中的一切东西都是对象,所以许多活动 变得更加简单,这个问题便是其中的一例. 除非真的需要代码,否则那个文件是不会载入的.通常,我们可认为除非那个类的一个对象构造完毕,  否则代码不会真 ...

  3. Part2-HttpClient官方教程-Chapter6-HTTP缓存(HTTP Caching)

    原文链接 6.1. 一般概念 HttpClient Cache提供了一个与HTTP / 1.1兼容的缓存层与HttpClient(浏览器缓存的Java等价物.)一起使用.该实现遵循责任链设计模式,其中 ...

  4. AGC025简要题解

    AGC025简要题解 B RGB Coloring 一道简单题,枚举即可. C Interval Game 考虑可以进行的操作只有两种,即左拉和右拉,连续进行两次相同的操作是没有用的. 左拉时肯定会选 ...

  5. upupw注入by pass

    http:' and updatexml(null,concat(0x5c,(/*!00000select SCHEMA_name*/from/*!information_schema*/.schem ...

  6. Linux 入门记录:十八、Linux 系统启动流程 + 单用户修改 root 密码 + GRUB 加密

    一.系统启动流程 一般来说,Linux 系统的启动流程是这样的: 1. 开机之后,位于计算机主板 ROM 芯片上的 BIOS 被最先读取,在进行硬件和内存的校验以及 CPU 的自检没有异常后, BIO ...

  7. python基础===Python 迭代器模块 itertools 简介

    本文转自:http://python.jobbole.com/85321/ Python提供了一个非常棒的模块用于创建自定义的迭代器,这个模块就是 itertools.itertools 提供的工具相 ...

  8. HDU 5627 Clarke and MST &意义下最大生成树 贪心

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5627 题意:Bestcoder的一道题,让你求&意义下的最大生成树. 解法: 贪心,我们从高位 ...

  9. HTML 知识点总结

    HTML基本语法 HTML标签 单标签 <标签名>或<标签名 /> 双标签 <标签名>内容</标签名> 跟标签也叫元素(根元素) 属性 属性属于标签 一 ...

  10. how to create view (windows)

    View Server List  IP address: 200.xx.xx.xx How to create a new view ssh new view server by your Unix ...