分析:
问题建立在字符串的子串上
而
SAM
S
A
M
包含了字符串的所有子串,所以可以往这个方向考虑
一开始我设计的状态:
f[i][j]
f
[
i
]
[
j
]
表示第
i
i
位是字符,用的最大步数
然后我就很直接的想到,在转移的时候可以找到以
j
j
为起点的子串
f[i+len−1][k]=max(f[i][j]+1)
f
[
i
+
l
e
n
−
1
]
[
k
]
=
m
a
x
(
f
[
i
]
[
j
]
+
1
)
可以看到n的范围比较大,那么一定需要用矩阵加速了
那就构造矩阵呗,但是模式串的长度长达1e5,矩阵的大小承受不起
所以矩阵元素一定不会是序列位置或者
SAM
S
A
M
上的结点
那我们就要另辟蹊径:
发现字符集比较小,就考虑能不能从不同字符的转移下手
我们仔细分析一下
S
S
串的构造方式:
假设串已知,ta的构造就可以看做是
S
S
和的一个匹配过程
如果匹配不上了,就重新从头匹配
我就突发奇想:能不能计算出从字符
x
x
转移到字符,不能继续转移需要的最小长度
(因为题目要求:构造所需的操作次数最大的字符串,我们希望操作次数大,那么从x转移到y的距离越小越好)
说的不太明白,看个例子:
T=DABCAABEC T = D A B C A A B E C
A A 没有这个后继,
因此如果 S S 中需要从转移到 D D ,那么一定需要一次操作
从转移到 C C 在中有4种情况: ABC,ABCAABEC,AABEC,ABEC A B C , A B C A A B E C , A A B E C , A B E C
A−−−>C=3 A − − − > C = 3
考虑构造转移矩阵
H
H
,表示:从字符
x
x
转移到字符,不能继续转移需要的最小长度
在构造矩阵
H
H
之前,我们需要用预处理一个
m
m
数组
表示结点 i i 转移到字符而不能继续转移的最短长度
m[i][j]=min(m[i][j],m[soni][j]+1) m [ i ] [ j ] = m i n ( m [ i ] [ j ] , m [ s o n i ] [ j ] + 1 )
void dfs(int x)
{
if (vis[x]) return;
vis[x]=1;
for (int i=0;i<4;i++)
if (ch[x][i]) dfs(ch[x][i]),m[x][i]=INF;
for (int i=0;i<4;i++)
{
if (!ch[x][i]){ //如果没有这个子结点
m[x][i]=1;
continue;
}
for (int j=0;j<4;j++)
m[x][j]=min(m[x][j],m[ch[x][i]][j]+1);
}
}
则:
H[i][j]=m[ch[root][i]][j]
H
[
i
]
[
j
]
=
m
[
c
h
[
r
o
o
t
]
[
i
]
]
[
j
]
转移:
H[i][j]=min(H[i][j]+H[j][k])
H
[
i
]
[
j
]
=
m
i
n
(
H
[
i
]
[
j
]
+
H
[
j
]
[
k
]
)
转移
x
x
步得到的:最少用步,以字符
i
i
为起点,字符为结尾的能得到的字符串长度
对于这种最小值最大的问题,一般都可以用二分答案来做
所以我们二分一个操作数,然后用矩阵快速幂完成,判断一下得到的最小长度是否>=n,如果大于等于n说明答案合法
tip
这次的矩阵加速不是矩阵乘法了
因为转移是:
f[i][j]=min(f[i][j],f[i][k]+f[k][j])
f
[
i
]
[
j
]
=
m
i
n
(
f
[
i
]
[
j
]
,
f
[
i
]
[
k
]
+
f
[
k
]
[
j
]
)
所以矩阵例计算的是加法取最小值
#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
#define ll long long
using namespace std;
const int N=300010;
const ll INF=1e18;
ll n,m[N][5];
int root=1,last=1,sz=1,len;
int dis[N],ch[N][5],fa[N],deep[N],vis[N];
char s[N];
struct node{
ll H[5][5];
node operator *(const node &a) const
{
node ans;
for (int i=0;i<4;i++)
for (int j=0;j<4;j++)
{
ans.H[i][j]=INF;
for (int k=0;k<4;k++)
ans.H[i][j]=min(ans.H[i][j],H[i][k]+a.H[k][j]);
}
return ans;
}
void clear()
{
memset(H,0,sizeof(H));
}
node KSM(ll b)
{
node ans=(*this),a=(*this);
b--;
while (b)
{
if (b&1) ans=ans*a;
a=a*a;
b>>=1;
}
return ans;
}
};
node H;
void insert(int x)
{
int now=++sz,pre=last;
last=now;
dis[now]=dis[pre]+1;
for (;pre&&!ch[pre][x];pre=fa[pre]) ch[pre][x]=now;
if (!pre) fa[now]=root;
else
{
int q=ch[pre][x];
if (dis[q]==dis[pre]+1) fa[now]=q;
else
{
int nows=++sz;
dis[nows]=dis[pre]+1;
memcpy(ch[nows],ch[q],sizeof(ch[q]));
fa[nows]=fa[q]; fa[q]=fa[now]=nows;
for (;pre&&ch[pre][x]==q;pre=fa[pre]) ch[pre][x]=nows;
}
}
}
void dfs(int x)
{
if (vis[x]) return;
vis[x]=1;
for (int i=0;i<4;i++)
if (ch[x][i]) dfs(ch[x][i]),m[x][i]=INF;
for (int i=0;i<4;i++)
{
if (!ch[x][i]){
m[x][i]=1;
continue;
}
for (int j=0;j<4;j++)
m[x][j]=min(m[x][j],m[ch[x][i]][j]+1);
}
}
void build()
{
dfs(root);
for (int i=0;i<4;i++)
for (int j=0;j<4;j++)
H.H[i][j]=m[ch[1][i]][j]; //由结点ch[1][i]转移到字符j
}
bool check(ll x)
{
node A=H.KSM(x);
ll mx=INF;
for (int i=0;i<4;i++)
for (int j=0;j<4;j++)
mx=min(mx,A.H[i][j]); //找到能够转移出的字符串
return mx>=n;
}
int main()
{
scanf("%lld",&n);
scanf("%s",s+1); len=strlen(s+1);
for (int i=1;i<=len;i++) insert(s[i]-'A');
build();
ll l=1,r=n,ans=n+1;
while (l<=r)
{
ll mid=(l+r)>>1;
if (check(mid)) ans=min(ans,mid),r=mid-1;
else l=mid+1;
}
if (ans==n+1) printf("0\n");
else printf("%lld\n",ans);
return 0;
}