对于一组字符串,如果后一个是前一个的真子串,就把它们称作是 ,它的 定义为字符串的数量。
给定长为 的字符串 ,在其中找到一个 ,满足所有字符串都是 的子串,前后两个字符串的位置不相交,且在 中依次出现,求最大的 。
时间限制5s, 空间限制512M
简单分析
答案最多只有1000,且答案满足二分性质.
有一个贪心的结论, 里面的字符串长度一定是 这样。
动态规划
设 表示最左边的子串从 位置开始时的答案,有以下性质:
- 这个子串一定长为 ,也就是说子串是
- 满足二分性质,即 等也可以找到对应的方案,可以朝左侧转移,而 不行。
- ,因为对于 的方案,如果把第一个字符全部扔掉,就可以构造出一个方案使得 ,所以 一定不会小于 .
边界:
转移:由上述性质2和性质3,在求 时,可以从 开始向下枚举 的值并 ,直到首次合法为止。
可以证明,时间复杂度为 .(证明方法同理于这篇文章的优化一)
check(i,k)
现在的目标是判断 的值能否等于 .
如果合法,这里的子串是 ,且它的后一个子串一定和 或 完全相同。
详细地,必须存在一个位置 ,满足:
- 或者
后缀数组
将问题映射到后缀数组上,第三个条件是要求目标位置和给定位置的lcp在一定范围内,可以二分 得到一个子数组,然后在子数组中解决问题。
现在问题又变成了,给定两个数组 、 ,若干次查询:
每次选定一个区间,问区间中是否存在一个位置 ,满足 且 。
这是一个数据结构问题,而且需要满足带修改+强制在线,据说能用主席树做,我没想到。
最后的优化
回忆一下, 的输入值 相比前一次 的变化,只会有两种:
- 减少了 , 不变,表示上一次 失败了。
- 增加了 , 减少了 ,表示 成功,开始求更左边一个位置的 值.
对于 来说,要么不变,要么减少 。
所以我们可以把所有 的位置的 值都存下来,然后 值记作 ,这样处理的时候就可以当作它们不存在。等到 合法了,再将存起来的值实装。
此时,问题就变成了单点修改+区间求max.
使用线段树处理即可.
复杂度分析
预处理:后缀数组+ST表,共
DP:状态数 ,转移一共需要 次
check:二分子数组 ,线段树操作
总时间复杂度:
实现细节
- check:
是否有一个位置j,满足dp[j]>=k-1,j>=i+k,max(lcp(j,i),lcp(j,i+1))>=k-1
在后缀数组上:
是否有一个位置j,满足某个区间内,dpsa[j]>=k-1, sa[j]>=i+k
让所有sa[j]<i+k的dpsa[j]先自闭. - 二分
二分height得到区间,然后求区间最大值是否大于等于k-1
lcp(j,i)>k-1的区间,是什么呢?
设左端点lef=rk[i], 如果height[lef]>=k-1,那么lef–
设右端点rig=rk[i], 如果height[rig+1]>=k-1, 那rig++
所以我需要找到mih(rk[i]+1,rig)>=k-1的最后一个rig
还有mih(lef+1,rk[i])>=k-1的第一个lef - 线段树
线段树维护的值是dpsa[],这个时候sa[j]==i+k,那么j=rk[i+k]。
各种带等于的边界纠结了不少时间。
ans初始化为0导致wa一次。
/* LittleFall : Hello! */
#include <bits/stdc++.h>
using namespace std; using ll = long long; inline int read();
const int M = 500016, MOD = 1000000007;
namespace SA
{
void st_init(int *arr, int n);
/* 后缀数组 */
int sa[M], rk[M], height[M]; //后缀三数组,sa和rk下标从0开始,height下标从1开始
int t1[M], t2[M], c[M]; // 用于基数排序的三个辅助数组
void build(char *str, int n, int m) // 构造后缀三数组,字符串下标从0开始,n表示长度,m表示字符集大小
{
str[n] = 0;
n++;
int i, j, p, *x = t1, *y = t2;
for(i = 0; i < m; i++) c[i] = 0;
for(i = 0; i < n; i++) c[x[i]=str[i]]++;
for(i = 1; i < m; i++) c[i] += c[i-1];
for(i = n-1; i >= 0; i--) sa[--c[x[i]]] = i;
for(j = 1; j <= n; j<<=1)
{
p = 0;
for(i = n-j; i < n; i++) y[p++] = i;
for(i = 0; i < n; i++) if(sa[i] >= j) y[p++] = sa[i]-j;
for(i = 0; i < m; i++) c[i] = 0;
for(i = 0; i < n; i++) c[x[y[i]]]++;
for(i = 1; i < m; i++) c[i] += c[i-1];
for(i = n-1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i];
swap(x, y);
p = 1; x[sa[0]] = 0;
for(i = 1; i < n; i++)
x[sa[i]] = (y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+j]==y[sa[i]+j]) ? p-1 : p++;
if(p >= n) break;
m = p;
}
n--;
for(int i = 0; i <= n; i++) rk[sa[i]] = i;
for(int i=0, j=0, k=0; i < n; i++)
{
if(k) k--;
j = sa[rk[i]-1];
while(str[i+k]==str[j+k]) k++;
height[rk[i]] = k;
}
st_init(height, n);
}
/* ST表 */
int lg[M], _n;
int table[20][M];
void st_init(int *arr, int n)
{
_n = n;
if(!lg[0])
{
lg[0]=-1;
for(int i=1;i<M;i++)
lg[i]=lg[i/2]+1;
}
for(int i=1; i<=n; ++i)
table[0][i] = arr[i];
for(int i=1; i<=lg[n]; ++i)
for(int j=1; j<=n; ++j)
if(j+(1<<i)-1 <= n)
table[i][j] = min(table[i-1][j], table[i-1][j+(1<<(i-1))]);
}
//height最小值
int mih(int l, int r)
{
int t = lg[r-l+1];
return min(table[t][l], table[t][r-(1<<t)+1]);
}
};
using SA::sa; using SA::mih;
using SA::rk;
struct SGT
{
int save[M<<2], m;
void build(int n) //含read
{
for(m=1; m<=n+1; m<<=1)
continue;
}
int query(int s, int t) //sum[s,t]
{
int ans = 0;
for(s+=m-1, t+=m+1; s^t^1; s>>=1, t>>=1)
{
if(~s&1) ans=max(ans, save[s^1]);
if( t&1) ans=max(ans, save[t^1]);
}
return ans;
}
void modify(int n, int val) //save[n]=val
{
for(save[n+=m]=val, n>>=1; n; n>>=1)
save[n] = max(save[n<<1], save[n<<1|1]);
}
}sgt; //线段树维护dpsa
int n;
char str[M];
int dp[M]; //dp[i]表示从i开始的答案
struct Checker
{
pair<int,int> binary_search(int i, int k)
{
int p1, p2;
{ //获得lef
int l=1, r=rk[i], ans=rk[i];
while(l<=r)
{
int m = (l+r)>>1;
if(mih(m+1, rk[i])>=k-1)
ans = m, r = m-1;
else l = m+1;
}
p1 = ans;
}
{
int l=rk[i]+1, r=n, ans=rk[i];
while(l<=r)
{
int m = (l+r)>>1;
if(mih(rk[i]+1, m)>=k-1)
ans = m, l = m+1;
else r = m-1;
}
p2 = ans;
}
return {p1,p2};
}
bool check(int i, int k)
{
sgt.modify(rk[i+k], dp[i+k]); //激活下标等于i+k的dp,
auto p1 = binary_search(i,k);
if(sgt.query(p1.first, p1.second)>=k-1) return 1;
auto p2 = binary_search(i+1,k);
if(sgt.query(p2.first, p2.second)>=k-1) return 1;
return 0;
}
}checker;
int main(void)
{
#ifdef _LITTLEFALL_
freopen("in.txt","r",stdin);
#endif
n = read();
sgt.build(n);
int ans = 1;
scanf("%s", str);
SA::build(str, n, 128);
dp[n-1] = 1;
for(int i=n-2; i>=0; --i)
{
for(int j=dp[i+1]+1; j>=0; --j)
if(checker.check(i,j)) dp[i]=j, j=-1;
ans = max(ans, dp[i]);
}
printf("%d\n",ans );
return 0;
}
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}