NOIP2020 T2 字符串匹配题解
首先考虑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\)了,美滋滋。
把暴力贴上来吧:
#include<bits/stdc++.h>
#define LL long long
#define N (1<<20)+50
using namespace std;
LL ans;
int t,len;
char a[N];
int toa[30],toc[30],jia[N],jic[N];
inline int qr()
{
char a=0;int x=0,w=1;
while(a<'0'||a>'9'){if(a=='-')w=-1;a=getchar();}
while(a<='9'&&a>='0'){x=(x<<3)+(x<<1)+(a^48);a=getchar();}
return x*w;
}
int main()
{
t=qr();
while(t--)
{
scanf("%s",(a+1));
len=strlen(a+1);
memset(toa,0,sizeof(toa));
memset(toc,0,sizeof(toc));
memset(jic,0,sizeof(jic));
memset(jia,0,sizeof(jia));
ans=0;
for(register int i=1;i<=len;i++)//计算L(i)
{
toa[a[i]-'a']++;
toa[a[i]-'a']&1 ? jia[i]=jia[i-1]+1 :jia[i]=jia[i-1]-1;
}
for(register int i=len;i>=1;i--)//计算R(i)
{
toc[a[i]-'a']++;
toc[a[i]-'a']&1 ? jic[i]=jic[i+1]+1 :jic[i]=jic[i+1]-1;
}
for(register int i=2;i<len;i++)
{
int flag=1;
for(register int k=1;k<i;k++)//特判k=1的情况
if(jia[k]<=jic[i+1])
ans++;
for(register int j=2;j*i<len;j++)//枚举循环次数
{
int le=(j-1)*i+1;
int re=j*i;
for(register int k=le;k<=re;k++)//判断循环节
if(a[k]!=a[k-i])
{
flag=0;
break;
}
if(flag)//循环成立枚举A,B分界点统计次数
{
for(register int k=1;k<i;k++)
if(jia[k]<=jic[re+1])
ans++;
}
else
break;//循环不成立退出
}
}
printf("%lld\n",ans);
continue;
}
return 0;
}
下面考虑优化时间复杂度。
前置知识
- 字符串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 :
#include<bits/stdc++.h>
#define LL long long
#define ULL unsigned long long
#define N (1<<20)+50
using namespace std;
LL ans;
int t,len;
char a[N];
int sm[N][27],sum[N];//分别对应题解中的L(i,j),R(i)
ULL Hash[N],jc[N],p=13331;//hash采取p进制,不过最好用双模数
//因为我太懒了,用了单模数哈希QAQ
inline int qr()//平平无奇的快读
{
char a=0;int x=0,w=1;
while(a<'0'||a>'9'){if(a=='-')w=-1;a=getchar();}
while(a<='9'&&a>='0'){x=(x<<3)+(x<<1)+(a^48);a=getchar();}
return x*w;
}
inline bool check(int l,int r,int L,int R)//字符串哈希判断相等
{
ULL hash1=(Hash[r]-Hash[l-1]*jc[r-l+1]);
ULL hash2=(Hash[R]-Hash[L-1]*jc[R-L+1]);
if(hash1==hash2)
return 1;
return 0;
}
int main()
{
t=qr();
jc[0]=1;
for(register int i=1;i<=(1<<20)+5;i++)
jc[i]=jc[i-1]*p;//处理p^i
while(t--)
{
scanf("%s",(a+1));
len=strlen(a+1);
ans=0;
int opk[27]={},val=0;
for(register int i=1;i<=len;i++)
Hash[i]=(Hash[i-1]*p+a[i]);//计算hash值
for(register int i=1;i<=len;i++)//预处理L(i,j)
{
for(register int j=0;j<=26;j++)
sm[i][j]=sm[i-1][j];//首先继承i-1时的前缀和
opk[(int)(a[i]-'a')+1]++;
opk[(int)(a[i]-'a')+1]&1?val++:val--;
sm[i][val]++;//加上统计的最新答案
}//这时sm(i,j)表示的仅为 1~i 中出现奇数次的字符数量=j的数量,于是需要再做一下前缀和
for(register int i=1;i<=len;i++)
for(register int j=1;j<=26;j++)
sm[i][j]+=sm[i][j-1];//现在sm(i,j)表示的就是 1~i 中出现奇数次的字符数量<=j的数量了
val=0;
memset(opk,0,sizeof(opk));
for(register int i=len;i>=1;i--)//预处理R(i,j)
{
opk[(int)(a[i]-'a')+1]++;
opk[(int)(a[i]-'a')+1]&1?val++:val--;
sum[i]=val;
}//没啥可说的
for(register int i=2;i<len;i++)
for(register int j=1;j*i<len;j++)//开始判循环节
{
int re=j*i+1;
if(check(1,i,i*(j-1)+1,j*i))//如果循环节成立
ans+=sm[i-1][sum[re]];//统计答案
else
break;//不成立直接退出即可
}
printf("%lld\n",ans);
continue;
}
return 0;
}
Update:
发现\(R\)(\(i\),\(j\))可以边进行计算边求,优化了一些常数(不开O2有96分,C++11就过了),代码如下:
#include<bits/stdc++.h>
#define LL long long
#define ULL unsigned long long
#define N (1<<20)+50
using namespace std;
LL ans;
int t,len;
char a[N];
int sm[27],sum[N];//分别对应题解中的L(i,j),R(i)
ULL Hash[N],jc[N],p=13331;//hash采取p进制,不过最好用双模数
//因为我太懒了,用了单模数哈希QAQ
inline int qr()//平平无奇的快读
{
char a=0;int x=0,w=1;
while(a<'0'||a>'9'){if(a=='-')w=-1;a=getchar();}
while(a<='9'&&a>='0'){x=(x<<3)+(x<<1)+(a^48);a=getchar();}
return x*w;
}
inline bool check(int l,int r,int L,int R)//字符串哈希判断相等
{
ULL hash1=(Hash[r]-Hash[l-1]*jc[r-l+1]);
ULL hash2=(Hash[R]-Hash[L-1]*jc[R-L+1]);
if(hash1==hash2)
return 1;
return 0;
}
int main()
{
t=qr();
jc[0]=1;
for(register int i=1;i<=(1<<20)+5;i++)
jc[i]=jc[i-1]*p;//处理p^i
while(t--)
{
scanf("%s",(a+1));
len=strlen(a+1);
ans=0;
int opk[27]={},val=0;
for(register int i=1;i<=len;i++)
Hash[i]=(Hash[i-1]*p+a[i]);//计算hash值
val=0;
for(register int i=len;i>=1;i--)//预处理R(i,j)
{
opk[(int)(a[i]-'a')+1]++;
opk[(int)(a[i]-'a')+1]&1?val++:val--;
sum[i]=val;
}//没啥可说的
val=0;
memset(sm,0,sizeof(sm));
memset(opk,0,sizeof(opk));
for(register int i=2;i<len;i++)
{
opk[(int)(a[i-1]-'a')+1]++;
opk[(int)(a[i-1]-'a')+1]&1?val++:val--;
for(register int j=val;j<=26;j++)
sm[j]++;//求R(i,j)
for(register int j=1;j*i<len;j++)//开始判循环节
{
int re=j*i+1;
if(check(1,i,i*(j-1)+1,j*i))//如果循环节成立
ans+=sm[sum[re]];//统计答案
else
break;//不成立直接退出即可
}
}
printf("%lld\n",ans);
continue;
}
return 0;
}
NOIP2020 T2 字符串匹配题解的更多相关文章
- KMP算法详解&&P3375 【模板】KMP字符串匹配题解
KMP算法详解: KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt(雾)提出的. 对于字符串匹配问题(such as 问你在abababb中有多少个 ...
- 洛谷 P3375 【模板】KMP字符串匹配 题解
KMP模板,就不解释了 #include<iostream> #include<cstdio> #include<cstring> #include<algo ...
- 题解-洛谷P7114 字符串匹配
题面 洛谷P7114 字符串匹配 \(T\) 组测试数据.给定字符串 \(S\),问有多少不同的非空字符串 \(A\),\(B\),\(C\) 满足 \(S=ABABAB...ABC\) 且 \(A\ ...
- 字符串匹配之KMP---全力解析
近日,一同学面试被问到字符串匹配算法,结果因为他使用了暴力法,直接就跪了(如今想想这种面试官真的是不合格的,陈皓的一篇文章说的非常好,点击阅读).字符串匹配方法大概有:BF(暴力破解法), 简化版的B ...
- COJN 0558 800600带通配符的字符串匹配
800600带通配符的字符串匹配 难度级别:B: 运行时间限制:1000ms: 运行空间限制:51200KB: 代码长度限制:2000000B 试题描述 通配符是一类键盘字符,当我们不知道真正字符或者 ...
- HDU 5716 带可选字符的多字符串匹配(ShiftAnd)
[题目链接] http://acm.hdu.edu.cn/showproblem.php?pid=5716 [题目大意] 给出一个字符串,找出其中所有的符合特定模式的子串位置,符合特定模式是指,该子串 ...
- 2018 ACM-ICPC 中国大学生程序设计竞赛线上赛 H题 Rock Paper Scissors Lizard Spock.(FFT字符串匹配)
2018 ACM-ICPC 中国大学生程序设计竞赛线上赛:https://www.jisuanke.com/contest/1227 题目链接:https://nanti.jisuanke.com/t ...
- CCF CSP 201409-3 字符串匹配
CCF计算机职业资格认证考试题解系列文章为meelo原创,请务必以链接形式注明本文地址 CCF CSP 201409-3 字符串匹配 问题描述 给出一个字符串和多行文字,在这些文字中找到字符串出现的那 ...
- 字符串匹配&Rabin-Karp算法讲解
问题描述: Rabin-Karp的预处理时间是O(m),匹配时间O( ( n - m + 1 ) m )既然与朴素算法的匹配时间一样,而且还多了一些预处理时间,那为什么我们还要学习这个算法呢?虽然Ra ...
随机推荐
- Unity 打包Shader优化
我们一直以来的项目Shader基本都会打包到一个package里面,游戏启动时会进行预加载这个Package,且预加载其中一些常用的Shader,最近新发现一个坑点,那就是shader依赖了特效的一些 ...
- rest framework Serializer fields
串行领域 在表单类中的每个字段不仅负责验证数据,同时也为"清洁" - 它以标准化格式一致. - Django文档 串行字段手柄的原始值和内部数据类型之间的转换.他们还应对验证输入值 ...
- Thymeleaf是个什么东东?
Thymeleaf是面向Web和独立环境的现代服务器端Java模板引擎,能够处理HTML,XML,JavaScript,CSS甚至纯文本. Thymeleaf的主要目标是提供一个优雅和高度可维护的创建 ...
- Python之word文档模板套用 - 真正的模板格式套用
Python之word文档模板套用: 1 ''' 2 #word模板套用2:套用模板 3 ''' 4 5 #导入所需库 6 from docx import Document 7 ''' 8 #另存w ...
- 网页客服思路以及QQ截图粘贴到聊天框功能
功能: 1.客服需登录进入客服页面.用户无需登录,进入用户页面,直接获取sessionId作为id值. 2.用户进入页面并且发送消息时,客服才会获取到该用户,并在左侧列表显示. 3.点击用户名即可切换 ...
- 分布式事务MSDTC使用时,需要的配置
服务器最终配置 DTC服务 组件 防火墙 这里,跟下面的解决方案有点差异,在添加2个规则之后,原本就有分布式相关的规则,也给开启了. 网上的解决办法 在服务里打开 Distributed Transa ...
- 【程序包管理】Linux软件管理之src源码安装编译
在很多时候我们需要自定义软件的特性,这时就需要用到源码安装.那么,网上有很多编译源码的工具,那么,我们怎么知道别人使用的是什么工具呢.其实我也不知道(*^▽^*). 那么本篇博客主要是写C代码的源码安 ...
- Spring Boot 与 Spring MVC到底有什么区别
前言 Spring 框架就像一个家族,有众多衍生产品例如 boot.security.jpa等等.但他们的基础都是Spring 的 ioc和 aop ioc 提供了依赖注入的容器 aop ,解决了面向 ...
- Error:java: JDK isn't specified for module 'xxx'异常的解决方法
问题描述 博主启动的项目的时候出现了一个这样的异常 解决方法 打开左上角这个Project Structure
- 干掉 powerdesigner,设计数据库表用它就够了
最近有个新项目刚过完需求,正式进入数据库表结构设计阶段,公司规定统一用数据建模工具 PowerDesigner.但我并不是太爱用这个工具,因为它的功能实在是太多了,显得很臃肿,而平时设计表用的也就那么 ...