- 给定一个长度为(n)的字符串(s)。
- 你有一个初始为空的串(S),你可以任意次将(s)加到(S)的末尾,若(s)的开头(i)个字符和(S)的结尾(i)个字符相同,可以选择不加入这相同的部分(即仅加入(s)第(i+1sim n)个字符)。
- 求(S)在([1,m])中可能达到的长度种数。
- 数据组数(le5),(nle5 imes10^5,mle10^{18})
(border)性质
显然,(S)的末尾(n)个字符始终是(s)。
因此,除了第一步必须加入整个(s)外,无论何时,我们能够加入一段长为(x)的字符串,当且仅当(n-x)是(s)的一个(border)。
于是先用(KMP)求出(s)的所有(border),那么就求出了单次可能加入的所有长度。
考虑到(border)的性质,所有的(border)可以划分为至多(O(logn))个等差数列。
则单次可能加入的所有长度也可以划分为至多(O(logn))个等差数列,不妨设第(i)个等差数列首项为(x_i),末项为(y_i),公差为(d_i),并记(l_i=frac{y_i-x_i}{d_i})。
同余最短路
依次考虑每一个等差数列(x,d,l)(定义见前面)。
这种问题其实有一个套路,就是同余最短路。
我们求出(f_i)表示可能达到的模(x)余(i)的最小长度,由于(x)是一个可加入长度,则(f_i+x,f_i+2x,..)都是可能达到的。
而我们每次可以加入(x+kcdot d(kin[1,l])),从每个点(i)向((i+d)\%x)连边,就意味着每个点可以向从它出发(l)步以内的点转移:
[f_j=min{f_i+x+(pos_j-pos_i) imes d}
]
根据(f_i-pos_i imes d)开一个单调队列优化转移即可。
具体实现中注意这张图实际上可以被分成(gcd(x,d))个环,对于每个环我们从环上最小的(f_i)断环为链开始转移(因为最小的(f_i)肯定无法被更新得更优)。
处理不同的等差数列之间需要切换模数,相当于每次可以加入(kcdot lst),从每个点(i)向((i+lst)\%x)连边,由于(k)没有了范围限制且转移时没有了(x)这个常数项,同样在最小的(f_i)断环为链,此时每次只要从上一个点转移即可,算是前面过程的简化版。
代码:(O(Tnlogn))
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 500000
#define LL long long
#define INF (LL)1e18
using namespace std;
int n;LL m;char s[N+5];I int gcd(CI x,CI y) {return y?gcd(y,x%y):x;}
int lst,q[N+5],c[N+5];LL f[N+5],g[N+5];I void Solve(CI x,CI y,CI d)//处理一个等差数列
{
RI i,j,r,s;for(i=0;i^lst;++i) g[i]=f[i],f[i]=INF;//记录原本的答案
for(i=0;i^lst;++i) f[g[i]%x]=min(f[g[i]%x],g[i]);//初始化新的答案
for(r=gcd(lst,x)-1;~r;--r)//切换模数,枚举每一个环
{
for(i=(r+lst)%x,s=r;i^r;i=(i+lst)%x) f[i]<f[s]&&(s=i);//找到最小f[i]所在位置
for(i=(s+lst)%x,j=s;i^s;j=i,i=(i+lst)%x) f[i]=min(f[i],f[j]+lst);//断环为链,每次从上一个点转移
}
if(lst=x,!d) return;RI l=(y-x)/d,H,T,ct;LL t;for(r=gcd(x,d)-1;~r;--r)//更新答案,枚举每一个环
{
for(i=(r+d)%x,s=r;i^r;i=(i+d)%x) f[i]<f[s]&&(s=i);//找到最小f[i]所在位置
for(i=(s+d)%x,q[H=T=1]=s,c[1]=ct=1;i^s;i=(i+d)%x)//断环为链
{
++ct;W(H<=T&&ct-c[H]>l) ++H;f[i]=min(f[i],f[q[H]]+x+(ct-c[H])*d);//从l步以内的点转移
W(H<=T&&f[q[T]]-1LL*c[T]*d>=f[i]-1LL*ct*d) --T;q[++T]=i,c[T]=ct;//维护单调队列
}
}
}
int nxt[N+5];I void KMP()//KMP求border
{
RI i,j;for(i=2,nxt[j=0]=-1;i<=n;s[j+1]==s[i]&&++j,nxt[i++]=j) W(j&&s[j+1]^s[i]) j=nxt[j];//求next数组
for(lst=n,i=nxt[n];~i;i=nxt[i]) if(!~nxt[i]) Solve(n-i,n-i,0);else//只剩一项
{j=i;W(~nxt[nxt[i]]&&i-nxt[i]==nxt[i]-nxt[nxt[i]]) i=nxt[i];Solve(n-j,n-(i=nxt[i]),j-nxt[j]);}//抠出一个等差数列
}
int main()
{
RI Tt,i;LL t=0;scanf("%d",&Tt);W(Tt--)
{
for(scanf("%d%lld%s",&n,&m,s+1),i=1;i<=n;++i) f[i]=INF;
for(KMP(),t=i=0;i^lst;++i) t+=m-n>=f[i]?(m-n-f[i])/lst+1:0;printf("%lld
",t);//枚举每种余数统计答案
}return 0;
}