首先考虑O(n^3)的暴力怎么写。

显然,可以枚举字符串\(A\)+\(B\)的右端点,左端点显然是1,暴力判断是否能与后面的字符构成循环节,对于满足

\(k*(A+B)+C=\) 整个字符串\((k \in Z)\)

的情况暴力枚举\(A\),\(B\)分界点,对于\(L(x)\le R(x)\)的情况统计答案即可,\(L(x)\)为\(1\)到\(n\)出现奇数次的字符数量,\(R(x)\)为\(x\)到\(n\)出现奇数次的字符数量。

其实这样由于\(A+B\)很难找到循环节,均摊下来时间复杂度在随机数据下远小于\(O(n^{3})\),可以拿到\(48pts\)了,美滋滋。

把暴力贴上来吧:
  1. #include<bits/stdc++.h>
  2. #define LL long long
  3. #define N (1<<20)+50
  4. using namespace std;
  5. LL ans;
  6. int t,len;
  7. char a[N];
  8. int toa[30],toc[30],jia[N],jic[N];
  9. inline int qr()
  10. {
  11. char a=0;int x=0,w=1;
  12. while(a<'0'||a>'9'){if(a=='-')w=-1;a=getchar();}
  13. while(a<='9'&&a>='0'){x=(x<<3)+(x<<1)+(a^48);a=getchar();}
  14. return x*w;
  15. }
  16. int main()
  17. {
  18. t=qr();
  19. while(t--)
  20. {
  21. scanf("%s",(a+1));
  22. len=strlen(a+1);
  23. memset(toa,0,sizeof(toa));
  24. memset(toc,0,sizeof(toc));
  25. memset(jic,0,sizeof(jic));
  26. memset(jia,0,sizeof(jia));
  27. ans=0;
  28. for(register int i=1;i<=len;i++)//计算L(i)
  29. {
  30. toa[a[i]-'a']++;
  31. toa[a[i]-'a']&1 ? jia[i]=jia[i-1]+1 :jia[i]=jia[i-1]-1;
  32. }
  33. for(register int i=len;i>=1;i--)//计算R(i)
  34. {
  35. toc[a[i]-'a']++;
  36. toc[a[i]-'a']&1 ? jic[i]=jic[i+1]+1 :jic[i]=jic[i+1]-1;
  37. }
  38. for(register int i=2;i<len;i++)
  39. {
  40. int flag=1;
  41. for(register int k=1;k<i;k++)//特判k=1的情况
  42. if(jia[k]<=jic[i+1])
  43. ans++;
  44. for(register int j=2;j*i<len;j++)//枚举循环次数
  45. {
  46. int le=(j-1)*i+1;
  47. int re=j*i;
  48. for(register int k=le;k<=re;k++)//判断循环节
  49. if(a[k]!=a[k-i])
  50. {
  51. flag=0;
  52. break;
  53. }
  54. if(flag)//循环成立枚举A,B分界点统计次数
  55. {
  56. for(register int k=1;k<i;k++)
  57. if(jia[k]<=jic[re+1])
  58. ans++;
  59. }
  60. else
  61. break;//循环不成立退出
  62. }
  63. }
  64. printf("%lld\n",ans);
  65. continue;
  66. }
  67. return 0;
  68. }

下面考虑优化时间复杂度。

前置知识
  • 字符串hash

没了

我们可以想到,时间主要浪费在以下两点:
  • 在查找循环节时暴力匹配显然不是一个明智之举
  • 查找\(A\),\(B\)分界时单一操作重复多次。
那么考虑优化:

显然,一个字符串出现奇数次的字符的数量\(\le\) \(26\) 废话,那么可以

维护一个类似前缀和的东西\(R\)(\(i\),\(j\))表示从\(1-i\)奇数次的字符的数量满足\(\le\) \(j\)的位置,用\(L\)(\(i\))表示从\(i-n\)奇数次的字符的数量。

于是,对可成立的A+B字符串判断复杂度变成了预处理\(O\)(\(27n\)),查询\(O\)(\(1\))。

其次,可以用字符串哈希或KMP判循环节,将判循环节复杂度优化为调和级数级即\(O\)(\(n\) \(\ln\) \(n\))。

加上这两个优化后复杂度大概在\(O( T(27n+n \ln n) )\)。

喜闻乐见的 Code :
  1. #include<bits/stdc++.h>
  2. #define LL long long
  3. #define ULL unsigned long long
  4. #define N (1<<20)+50
  5. using namespace std;
  6. LL ans;
  7. int t,len;
  8. char a[N];
  9. int sm[N][27],sum[N];//分别对应题解中的L(i,j),R(i)
  10. ULL Hash[N],jc[N],p=13331;//hash采取p进制,不过最好用双模数
  11. //因为我太懒了,用了单模数哈希QAQ
  12. inline int qr()//平平无奇的快读
  13. {
  14. char a=0;int x=0,w=1;
  15. while(a<'0'||a>'9'){if(a=='-')w=-1;a=getchar();}
  16. while(a<='9'&&a>='0'){x=(x<<3)+(x<<1)+(a^48);a=getchar();}
  17. return x*w;
  18. }
  19. inline bool check(int l,int r,int L,int R)//字符串哈希判断相等
  20. {
  21. ULL hash1=(Hash[r]-Hash[l-1]*jc[r-l+1]);
  22. ULL hash2=(Hash[R]-Hash[L-1]*jc[R-L+1]);
  23. if(hash1==hash2)
  24. return 1;
  25. return 0;
  26. }
  27. int main()
  28. {
  29. t=qr();
  30. jc[0]=1;
  31. for(register int i=1;i<=(1<<20)+5;i++)
  32. jc[i]=jc[i-1]*p;//处理p^i
  33. while(t--)
  34. {
  35. scanf("%s",(a+1));
  36. len=strlen(a+1);
  37. ans=0;
  38. int opk[27]={},val=0;
  39. for(register int i=1;i<=len;i++)
  40. Hash[i]=(Hash[i-1]*p+a[i]);//计算hash值
  41. for(register int i=1;i<=len;i++)//预处理L(i,j)
  42. {
  43. for(register int j=0;j<=26;j++)
  44. sm[i][j]=sm[i-1][j];//首先继承i-1时的前缀和
  45. opk[(int)(a[i]-'a')+1]++;
  46. opk[(int)(a[i]-'a')+1]&1?val++:val--;
  47. sm[i][val]++;//加上统计的最新答案
  48. }//这时sm(i,j)表示的仅为 1~i 中出现奇数次的字符数量=j的数量,于是需要再做一下前缀和
  49. for(register int i=1;i<=len;i++)
  50. for(register int j=1;j<=26;j++)
  51. sm[i][j]+=sm[i][j-1];//现在sm(i,j)表示的就是 1~i 中出现奇数次的字符数量<=j的数量了
  52. val=0;
  53. memset(opk,0,sizeof(opk));
  54. for(register int i=len;i>=1;i--)//预处理R(i,j)
  55. {
  56. opk[(int)(a[i]-'a')+1]++;
  57. opk[(int)(a[i]-'a')+1]&1?val++:val--;
  58. sum[i]=val;
  59. }//没啥可说的
  60. for(register int i=2;i<len;i++)
  61. for(register int j=1;j*i<len;j++)//开始判循环节
  62. {
  63. int re=j*i+1;
  64. if(check(1,i,i*(j-1)+1,j*i))//如果循环节成立
  65. ans+=sm[i-1][sum[re]];//统计答案
  66. else
  67. break;//不成立直接退出即可
  68. }
  69. printf("%lld\n",ans);
  70. continue;
  71. }
  72. return 0;
  73. }

Update:

发现\(R\)(\(i\),\(j\))可以边进行计算边求,优化了一些常数(不开O2有96分,C++11就过了),代码如下:

  1. #include<bits/stdc++.h>
  2. #define LL long long
  3. #define ULL unsigned long long
  4. #define N (1<<20)+50
  5. using namespace std;
  6. LL ans;
  7. int t,len;
  8. char a[N];
  9. int sm[27],sum[N];//分别对应题解中的L(i,j),R(i)
  10. ULL Hash[N],jc[N],p=13331;//hash采取p进制,不过最好用双模数
  11. //因为我太懒了,用了单模数哈希QAQ
  12. inline int qr()//平平无奇的快读
  13. {
  14. char a=0;int x=0,w=1;
  15. while(a<'0'||a>'9'){if(a=='-')w=-1;a=getchar();}
  16. while(a<='9'&&a>='0'){x=(x<<3)+(x<<1)+(a^48);a=getchar();}
  17. return x*w;
  18. }
  19. inline bool check(int l,int r,int L,int R)//字符串哈希判断相等
  20. {
  21. ULL hash1=(Hash[r]-Hash[l-1]*jc[r-l+1]);
  22. ULL hash2=(Hash[R]-Hash[L-1]*jc[R-L+1]);
  23. if(hash1==hash2)
  24. return 1;
  25. return 0;
  26. }
  27. int main()
  28. {
  29. t=qr();
  30. jc[0]=1;
  31. for(register int i=1;i<=(1<<20)+5;i++)
  32. jc[i]=jc[i-1]*p;//处理p^i
  33. while(t--)
  34. {
  35. scanf("%s",(a+1));
  36. len=strlen(a+1);
  37. ans=0;
  38. int opk[27]={},val=0;
  39. for(register int i=1;i<=len;i++)
  40. Hash[i]=(Hash[i-1]*p+a[i]);//计算hash值
  41. val=0;
  42. for(register int i=len;i>=1;i--)//预处理R(i,j)
  43. {
  44. opk[(int)(a[i]-'a')+1]++;
  45. opk[(int)(a[i]-'a')+1]&1?val++:val--;
  46. sum[i]=val;
  47. }//没啥可说的
  48. val=0;
  49. memset(sm,0,sizeof(sm));
  50. memset(opk,0,sizeof(opk));
  51. for(register int i=2;i<len;i++)
  52. {
  53. opk[(int)(a[i-1]-'a')+1]++;
  54. opk[(int)(a[i-1]-'a')+1]&1?val++:val--;
  55. for(register int j=val;j<=26;j++)
  56. sm[j]++;//求R(i,j)
  57. for(register int j=1;j*i<len;j++)//开始判循环节
  58. {
  59. int re=j*i+1;
  60. if(check(1,i,i*(j-1)+1,j*i))//如果循环节成立
  61. ans+=sm[sum[re]];//统计答案
  62. else
  63. break;//不成立直接退出即可
  64. }
  65. }
  66. printf("%lld\n",ans);
  67. continue;
  68. }
  69. return 0;
  70. }

NOIP2020 T2 字符串匹配题解的更多相关文章

  1. KMP算法详解&&P3375 【模板】KMP字符串匹配题解

    KMP算法详解: KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt(雾)提出的. 对于字符串匹配问题(such as 问你在abababb中有多少个 ...

  2. 洛谷 P3375 【模板】KMP字符串匹配 题解

    KMP模板,就不解释了 #include<iostream> #include<cstdio> #include<cstring> #include<algo ...

  3. 题解-洛谷P7114 字符串匹配

    题面 洛谷P7114 字符串匹配 \(T\) 组测试数据.给定字符串 \(S\),问有多少不同的非空字符串 \(A\),\(B\),\(C\) 满足 \(S=ABABAB...ABC\) 且 \(A\ ...

  4. 字符串匹配之KMP---全力解析

    近日,一同学面试被问到字符串匹配算法,结果因为他使用了暴力法,直接就跪了(如今想想这种面试官真的是不合格的,陈皓的一篇文章说的非常好,点击阅读).字符串匹配方法大概有:BF(暴力破解法), 简化版的B ...

  5. COJN 0558 800600带通配符的字符串匹配

    800600带通配符的字符串匹配 难度级别:B: 运行时间限制:1000ms: 运行空间限制:51200KB: 代码长度限制:2000000B 试题描述 通配符是一类键盘字符,当我们不知道真正字符或者 ...

  6. HDU 5716 带可选字符的多字符串匹配(ShiftAnd)

    [题目链接] http://acm.hdu.edu.cn/showproblem.php?pid=5716 [题目大意] 给出一个字符串,找出其中所有的符合特定模式的子串位置,符合特定模式是指,该子串 ...

  7. 2018 ACM-ICPC 中国大学生程序设计竞赛线上赛 H题 Rock Paper Scissors Lizard Spock.(FFT字符串匹配)

    2018 ACM-ICPC 中国大学生程序设计竞赛线上赛:https://www.jisuanke.com/contest/1227 题目链接:https://nanti.jisuanke.com/t ...

  8. CCF CSP 201409-3 字符串匹配

    CCF计算机职业资格认证考试题解系列文章为meelo原创,请务必以链接形式注明本文地址 CCF CSP 201409-3 字符串匹配 问题描述 给出一个字符串和多行文字,在这些文字中找到字符串出现的那 ...

  9. 字符串匹配&Rabin-Karp算法讲解

    问题描述: Rabin-Karp的预处理时间是O(m),匹配时间O( ( n - m + 1 ) m )既然与朴素算法的匹配时间一样,而且还多了一些预处理时间,那为什么我们还要学习这个算法呢?虽然Ra ...

随机推荐

  1. 网络编程-python实现-socket(1.1.1)

    @ 目录 1.不同电脑进程之间如何通信 2.什么是socket 3.创建socket 1.不同电脑进程之间如何通信 利用ip地址 协议 端口 标识网络的进程,网络中的进程通信就可以利用这个标志与其他进 ...

  2. iframe高度自动随着子页面的高度变化而变化(不止要在iframe标签里加上this.height=this.contentWindow.document.body.scrollHeight)

    最近使用iframe整合页面遇到一些难题,走了很多弯路才解决,借此记录一下: 1 <!-- 页面主体内容 --> 2 <div class="iframe-wrapper& ...

  3. Python将文件夹下的文件名写入excel方便统计

    如题,贴代码: 1 ''' 2 #python将某文件夹下的文件名存储到excel中 3 ''' 4 5 #导入所需模块 6 import os 7 import xlwt 8 9 #定义要处理的文件 ...

  4. 庐山真面目之九微服务架构 NetCore 基于 Docker 基础镜像和挂载文件部署

    庐山真面目之九微服务架构 NetCore 基于 Docker 基础镜像和挂载文件部署 一.简介      我们在上一篇文章<庐山真面目之八微服务架构 NetCore 基于 Dockerfile ...

  5. 【对线面试官】Java注解

    public void send(String userName) {  try {    // qps 上报    qps(params);    long startTime = System.c ...

  6. C# 数组 ArrayList List<T>区别

    System.Collenctions和System.Collenctions.Generic 中提供了很多列表.集合和数组.例如:List<T>集合,数组Int[],String[] . ...

  7. (六)、mkdir--创建目录make directory

    一.命令详解与命令格式 在文件系统中创建新的目录, 格式:mkdir   [-选项]   目录名 目录名既可以是相对路径名,也可是相对路径名 选项: -p或者--parent,创建指定路径中所有不存在 ...

  8. 关于eclipse反编译插件不起作用问题的解决

    1.首先我的eclipse版本是 Version: Photon Release (4.8.0),小伙伴们可以通过 help>>About eclipse IDE 来查看自己的eclips ...

  9. 关于一台电脑使用多个GitHub账户管理代码的记录

    @参考原文 记录这个操作是因为需要将一些代码放出去到公共仓库上以便使用github pages线上预览今天页面的功能,但是碰到了一个很狗血的问题,虽然最后莫名其妙的解决了,但还是不知缘由,希望能在评论 ...

  10. rocketmq 架构设计

    1 消息存储 消息存储是RocketMQ中最为复杂和最为重要的一部分,本节将分别从RocketMQ的消息存储整体架构.PageCache与Mmap内存映射以及RocketMQ中两种不同的刷盘方式三方面 ...