题目链接:https://www.luogu.com.cn/problem/P4156

题目大意:每一开始你有一个字符串 $s$ ,然后你可以在字符串末尾加入 $s$ 并删除末尾一个 $s$ 的 Border ,问你最终能得到多少个不超过 $w$ 的不同长度。

做法

首先,如何计数?观察到我们只要知道对于 $\forall k\in \{0,1,…,n-1\}$ ,最小的长度 $len_k$ 满足:$len_k\equiv k\mod{n}$ 。

这样就能够在 $O(n)$ 的时间内计算答案了。

这时一个很暴力的想法是求出所有的 Border 然后做同余最短路,时间复杂度 $O(n^2)$ 。

这个时候就要利用 Border 的性质做一些优化了,Border 有一个性质:能够把所有 Border 划分成恰好 $\log$ 段等差数列。

不妨将长度设为 $l,l+d,l+2d,…,l+(s-1)d$ 。

这时候还要观察到一个事情:如果把这个问题想象成背包,这个背包的体积和价值是一样的,这能够产生一个什么性质呢?不妨认为 $len_k\equiv t\mod{l}$ ,我们只要先在$\mod{l}$ 意义下跑出最小的 $len’_t$ ,然后再用 $len’_t$ 去更新 $len_k$ 即可。

前一部分可以用单调队列,后一部分连单调队列都不用。

时间复杂度:$O(Tn\log{n})$ 。

空间复杂度:$O(n)$ 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
const int N=5e5+5;
const int NN=1e6+5;
const LL inf=1e18+1;
int gcd(int x,int y){return !x?y:gcd(y%x,x);}
int fail[N],n;LL m,dp[N],tmp[N];
char st[N];
PII lis[NN];
int head,tail;
void solve(int l,int s,int d){
for(int i=0;i<l;i++)tmp[i]=inf+1;
for(int i=0;i<n;i++)tmp[dp[i]%l]=min(tmp[dp[i]%l],dp[i]);
int g=gcd(l,d);
for(int i=0;i<g;i++){
head=1;tail=0;
int now=i;
for(int lim=l/g*2,ti=0;lim;lim--,ti++){
while(head<=tail && ti-lis[head].second+1>s)head++;
if(head<=tail)tmp[now]=min(tmp[now],tmp[lis[head].first]+1ll*(ti-lis[head].second)*d+l);
while(head<=tail && tmp[lis[tail].first]+(ti-lis[tail].second)*d>tmp[now])tail--;
lis[++tail]={now,ti};
now=(now+d)%l;
}
}
for(int i=0;i<l;i++){
dp[tmp[i]%n]=min(dp[tmp[i]%n],tmp[i]);
}
g=gcd(l,n);
for(int i=0;i<g;i++){
int now=i;LL mi=inf;
for(int lim=n/g*2;lim;lim--){
dp[now]=min(dp[now],mi);
mi=min(mi,dp[now])+l;
now=(now+l)%n;
}
}
}
int main(){
int T;scanf("%d",&T);
while(T--){
scanf("%d%lld",&n,&m);
scanf("%s",st+1);
if(m<n){
printf("0\n");
continue;
}
m-=n;
fail[0]=-1;
int now=0;
for(int i=2;i<=n;i++){
while(now!=-1 && st[now+1]!=st[i])now=fail[now];
fail[i]=++now;
}
for(int i=0;i<n;i++)dp[i]=inf+1;
dp[0]=0;
now=fail[n];
while(now){
int nex=fail[now];
if(nex+nex<now)solve(n-now,1,1),now=nex;
else{
int p=now-nex;
int l=now%p+p;
solve(n-now,(now-l)/p+1,p);
now=l;
}
}
LL ans=0;
for(int i=0;i<n;i++){
if(dp[i]<=m)ans+=(m-dp[i])/n+1;
}
printf("%lld\n",ans);
}


return 0;
}

我没做出来,我的评价是:菜就多练。

训,狠狠地训。