题目链接: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; }
|
我没做出来,我的评价是:菜就多练。
训,狠狠地训。