[关闭]
@xiaoziyao 2021-08-03T14:58:53.000000Z 字数 2306 阅读 777

P4156 [WC2016]论战捆竹竿 解题报告

解题报告


看到初二的学弟已经开始卷这些了,非常恐怖。这就是我校初二真实实力吗?

P4156 [WC2016]论战捆竹竿解题报告:

更好的阅读体验

题意

给定一个长度为 的字符串 ,一开始有一个空串,每次可以将一个新的 去掉一个Border后拼上去,求长度区间 内有多少长度可以被拼出来。(

分析

妙妙题。

那么我们用 kmp 求出所有 Border,那么就可以得到所有周期 ,我们的问题转化为求 在区间 中可以有多少取值。

我们考虑随便取一个不小于所有 的模数 ,对于所有 ,求出最小的 使得 ,然后就可以直接算了。

这明显是同余最短路模型,我们如果直接取所有 的最大值为模数,让所有点 都连若干条边到 跑一遍最短路复杂度就是 ,有暴力分。

我们肯定不会把所有边建出来,于是我们考虑另一种暴力,对于每个 单独将它的边更新,每次继承上一次的最短路数组。

但是如何更新仍然是问题,不难发现对于串长 最短路图可以分成 个部分,每个部分之间没有边相连,且每个部分事实上就是从每个位置开始每次向后走 步的所有点,那么我们就可以把整个图变成若干个部分,每个部分从最小的位置一步步更新到后面直到重合就好了,但这样仍然是 的。

事实上,一个简单的结论可以大大降低复杂度:一个字符串所有的 Border 排序后,长度会形成 个等差数列。

这启发我们将等差数列放在一起跑最短路,具体地,我们发现对于一个首项为 ,公差为 ,项数为 的等差数列,我们并不好处理首项,于是可以直接把首项当成一个公差为 ,项数为 的等差数列的公差处理。

此时不难发现忽略了首项长度后,一个长度为 的等差数列就是向前连 条边,这个东西可以直接单调队列维护。

代码也很好写,时间复杂度:

代码

  1. #include<stdio.h>
  2. #include<iostream>
  3. using namespace std;
  4. const int maxn=500005;
  5. int T,n,cs,pos;
  6. int p[maxn],c[maxn];
  7. long long w,ans;
  8. long long dis[maxn],tmp[maxn];
  9. pair<int,long long>q[maxn];
  10. string s;
  11. int gcd(int a,int b){
  12. return b==0? a:gcd(b,a%b);
  13. }
  14. void getborder(){
  15. cs=0,p[0]=0;
  16. for(int i=1;i<n;i++){
  17. int j=p[i-1];
  18. while(j&&s[i]!=s[j])
  19. j=p[j-1];
  20. if(s[i]==s[j])
  21. j++;
  22. p[i]=j;
  23. }
  24. for(int i=p[n-1];i;i=p[i-1])
  25. c[++cs]=n-i;
  26. c[++cs]=n;
  27. }
  28. void getdis(int newpos,int len,int dif,int tot){
  29. int mod=c[newpos],g=gcd(dif,mod);
  30. for(int i=0;i<g;i++){
  31. int st=i;
  32. for(int j=(i+dif)%mod;j!=i;j=(j+dif)%mod)
  33. if(dis[j]<dis[st])
  34. st=j;
  35. int l=1,r=0;
  36. q[++r]=make_pair(0,dis[st]);
  37. for(int j=(st+dif)%mod,k=1;j!=st;j=(j+dif)%mod,k++){
  38. while(l<=r&&k-q[l].first>tot)
  39. l++;
  40. dis[j]=min(dis[j],q[l].second+1ll*(k-q[l].first)*dif+len);
  41. while(l<=r&&q[r].second+1ll*(k-q[r].first)*dif>=dis[j])
  42. r--;
  43. q[++r]=make_pair(k,dis[j]);
  44. }
  45. }
  46. pos=newpos;
  47. }
  48. void solve(){
  49. for(int i=0;i<c[1];i++)
  50. dis[i]=w+1;
  51. pos=1,dis[n%c[1]]=n;
  52. int l=1,r;
  53. while(l<cs){
  54. for(r=l+1;r<=cs&&c[r]-c[r-1]==c[l+1]-c[l];r++);
  55. r--;
  56. if(l<r)
  57. getdis(l,c[l],c[l+1]-c[l],r-l);
  58. if(r+1<=cs){
  59. for(int i=0;i<c[r+1];i++)
  60. tmp[i]=w+1;
  61. for(int i=0;i<c[l];i++)
  62. tmp[dis[i]%c[r+1]]=min(tmp[dis[i]%c[r+1]],dis[i]);
  63. for(int i=0;i<c[r+1];i++)
  64. dis[i]=tmp[i];
  65. getdis(r+1,0,c[l],1);
  66. }
  67. l=r+1;
  68. }
  69. }
  70. int main(){
  71. scanf("%d",&T);
  72. while(T--){
  73. scanf("%d%lld",&n,&w),cin>>s;
  74. getborder(),solve();
  75. ans=0;
  76. for(int i=0;i<c[pos];i++)
  77. if(dis[i]<=w)
  78. ans+=(w-dis[i])/c[pos]+1;
  79. printf("%lld\n",ans);
  80. }
  81. return 0;
  82. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注