@xiaoziyao
2021-08-03T22:58:53.000000Z
字数 2306
阅读 972
解题报告
看到初二的学弟已经开始卷这些了,非常恐怖。这就是我校初二真实实力吗?
P4156 [WC2016]论战捆竹竿解题报告:
给定一个长度为 的字符串 ,一开始有一个空串,每次可以将一个新的 去掉一个Border后拼上去,求长度区间 内有多少长度可以被拼出来。()
妙妙题。
那么我们用 kmp 求出所有 Border,那么就可以得到所有周期 ,我们的问题转化为求 在区间 中可以有多少取值。
我们考虑随便取一个不小于所有 的模数 ,对于所有 ,求出最小的 使得 ,然后就可以直接算了。
这明显是同余最短路模型,我们如果直接取所有 的最大值为模数,让所有点 都连若干条边到 跑一遍最短路复杂度就是 ,有暴力分。
我们肯定不会把所有边建出来,于是我们考虑另一种暴力,对于每个 单独将它的边更新,每次继承上一次的最短路数组。
但是如何更新仍然是问题,不难发现对于串长 最短路图可以分成 个部分,每个部分之间没有边相连,且每个部分事实上就是从每个位置开始每次向后走 步的所有点,那么我们就可以把整个图变成若干个部分,每个部分从最小的位置一步步更新到后面直到重合就好了,但这样仍然是 的。
事实上,一个简单的结论可以大大降低复杂度:一个字符串所有的 Border 排序后,长度会形成 个等差数列。
这启发我们将等差数列放在一起跑最短路,具体地,我们发现对于一个首项为 ,公差为 ,项数为 的等差数列,我们并不好处理首项,于是可以直接把首项当成一个公差为 ,项数为 的等差数列的公差处理。
此时不难发现忽略了首项长度后,一个长度为 的等差数列就是向前连 条边,这个东西可以直接单调队列维护。
代码也很好写,时间复杂度:。
#include<stdio.h>
#include<iostream>
using namespace std;
const int maxn=500005;
int T,n,cs,pos;
int p[maxn],c[maxn];
long long w,ans;
long long dis[maxn],tmp[maxn];
pair<int,long long>q[maxn];
string s;
int gcd(int a,int b){
return b==0? a:gcd(b,a%b);
}
void getborder(){
cs=0,p[0]=0;
for(int i=1;i<n;i++){
int j=p[i-1];
while(j&&s[i]!=s[j])
j=p[j-1];
if(s[i]==s[j])
j++;
p[i]=j;
}
for(int i=p[n-1];i;i=p[i-1])
c[++cs]=n-i;
c[++cs]=n;
}
void getdis(int newpos,int len,int dif,int tot){
int mod=c[newpos],g=gcd(dif,mod);
for(int i=0;i<g;i++){
int st=i;
for(int j=(i+dif)%mod;j!=i;j=(j+dif)%mod)
if(dis[j]<dis[st])
st=j;
int l=1,r=0;
q[++r]=make_pair(0,dis[st]);
for(int j=(st+dif)%mod,k=1;j!=st;j=(j+dif)%mod,k++){
while(l<=r&&k-q[l].first>tot)
l++;
dis[j]=min(dis[j],q[l].second+1ll*(k-q[l].first)*dif+len);
while(l<=r&&q[r].second+1ll*(k-q[r].first)*dif>=dis[j])
r--;
q[++r]=make_pair(k,dis[j]);
}
}
pos=newpos;
}
void solve(){
for(int i=0;i<c[1];i++)
dis[i]=w+1;
pos=1,dis[n%c[1]]=n;
int l=1,r;
while(l<cs){
for(r=l+1;r<=cs&&c[r]-c[r-1]==c[l+1]-c[l];r++);
r--;
if(l<r)
getdis(l,c[l],c[l+1]-c[l],r-l);
if(r+1<=cs){
for(int i=0;i<c[r+1];i++)
tmp[i]=w+1;
for(int i=0;i<c[l];i++)
tmp[dis[i]%c[r+1]]=min(tmp[dis[i]%c[r+1]],dis[i]);
for(int i=0;i<c[r+1];i++)
dis[i]=tmp[i];
getdis(r+1,0,c[l],1);
}
l=r+1;
}
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%lld",&n,&w),cin>>s;
getborder(),solve();
ans=0;
for(int i=0;i<c[pos];i++)
if(dis[i]<=w)
ans+=(w-dis[i])/c[pos]+1;
printf("%lld\n",ans);
}
return 0;
}