【2019 CCPC 秦皇岛】J - MUV LUV EXTRA
原题:
题意:
给你两个整数a和b,再给你一个正小数,整数部分忽略不计,只考虑小数部分的循环节,对于所有可能的循环节,令其长度为l,在小数部分循环出现的长度为p,最后一个循环节允许不完整,但是缺少的部分不计入循环长度
问你a*p-b*l的最小值是多少
考虑的循环节必须至少在小数部分中出现一次,小数部分的前缀可以不属于循环部分
这题也是一眼很棘手,和后缀数组和后缀自动机似乎都有相似之处
但是实际做法是kmp
翻转原串是关键思路,这样保证了能把原串前缀的不是循环部分的部分忽略掉,从而方便地利用next数组的性质
把小数部分串反过来,那么这个串的前缀就一定是循环部分
因为最后循环节允许不完整,所以考虑把一个循环节分成AB两部分,其中B是最后一个循环节缺的部分
考虑next数组的性质:next[i]表示子串[1,i]最长的前后缀长度,使得此长度的前后缀相等
那么对于ACA形式的串,其中A指最长的相同前后缀,把C接到A后边就可以变成ACA(C)的循环形式,前后缀有相交情况时同理
对于循环部分[1,i],出现长度就是i,出现长度固定,只需求出此时长度最小的循环节
由next的性质,保证[1,i-next[i]]是最小的循环节
可以反证,考虑ABABABABA,如果它被i-nxt[i]和next[i]+1划分为ABA BAB ABA,即认为[1,6]和[4,9]是相同的前后缀,且有多余的循环节被统计到了[1,i-nxt[i]]和[nxt[i]+1,i]里
那么此时可以从[1,i-nxt[i]]和[nxt[i],i]中各拿出一个循环节放到中间,易证此时仍然符合next[i]的性质,即之前求出的next[i]是错误的
所以得证,[1,i-next[i]]一定是最小的循环节
那么把小数部分倒过来,从左到右扫一遍,每次根据next[i]计算,然后统计到答案即可
代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 #include<cmath> 6 using namespace std; 7 #define LL long long 8 LL a,b; 9 char s[11000000]; 10 int c[11000000],n=0; 11 int nxt[11000000]; 12 LL ans; 13 void gtnxt(){ 14 int tmp=0; 15 nxt[1]=0; 16 for(int i=2;i<=n;++i){ 17 while(tmp&&c[tmp+1]!=c[i]) tmp=nxt[tmp]; 18 if(c[tmp+1]==c[i]) ++tmp; 19 nxt[i]=tmp; 20 /*if(nxt[i]){ 21 if(nxt[i]>=i-nxt[i]+1) ans=max(ans,a*i-b*(i-nxt[i])); 22 if(nxt[i]==i-nxt[i]) ans=max(ans,a*i-b*nxt[i]); 23 ans=max(ans,a*i*/ 24 if(nxt[i]) ans=max(ans,a*i-b*(i-nxt[i])); 25 } 26 } 27 int main(){ 28 //freopen("ddd.in","r",stdin); 29 while(scanf("%lld%lld",&a,&b)!=EOF){ 30 ans=-1e18,n=0; 31 scanf("%s",s); 32 for(int i=strlen(s)-1;i>=0;--i){ 33 if(s[i]=='.') break; 34 c[++n]=s[i]-'0'; 35 } 36 if(a-b>0) ans=(a-b)*n; 37 else ans=a-b; 38 gtnxt(); 39 cout<<ans<<endl; 40 /*for(int i=1;i<=n;++i) cout<<c[i]<<" "; 41 cout<<endl; 42 for(int i=1;i<=n;++i) cout<<nxt[i]<<" "; 43 cout<<endl;*/ 44 } 45 return 0; 46 }