1.暴力哈希(60分)
ybt
未通过
测试点 | 结果 | 内存 | 时间 |
测试点1 | 答案正确 | 336KB | 0MS |
测试点2 | 答案正确 | 332KB | 1MS |
测试点3 | 答案正确 | 388KB | 4MS |
测试点4 | 运行超时 | 992KB | 2006MS |
测试点5 | 运行超时 | 1392KB | 2014MS |
测试点6 | 运行超时 | 1688KB | 2004MS |
测试点7 | 运行超时 | 1824KB | 1995MS |
测试点8 | 运行超时 | 4320KB | 1998MS |
测试点9 | 运行超时 | 5304KB | 1998MS |
测试点10 | 运行超时 | 6484KB | 1998MS |
测试点11 | 答案正确 | 344KB | 1MS |
测试点12 | 答案正确 | 344KB | 2MS |
测试点13 | 答案正确 | 344KB | 2MS |
测试点14 | 答案正确 | 332KB | 2MS |
测试点15 | 答案正确 | 344KB | 1MS |
测试点16 | 答案正确 | 336KB | 2MS |
测试点17 | 答案正确 | 476KB | 127MS |
测试点18 | 答案正确 | 512KB | 227MS |
测试点19 | 答案正确 | 348KB | 0MS |
测试点20 | 答案正确 | 500KB | 67MS |
测试点21 | 答案正确 | 500KB | 223MS |
测试点22 | 运行超时 | 1992KB | 2003MS |
测试点23 | 答案正确 | 492KB | 1618MS |
测试点24 | 运行超时 | 504KB | 1991MS |
测试点25 | 答案正确 | 464KB | 1396MS |
测试点26 | 答案正确 | 508KB | 1967MS |
测试点27 | 运行超时 | 8628KB | 1997MS |
测试点28 | 运行超时 | 8624KB | 1995MS |
测试点29 | 运行超时 | 8084KB | 1998MS |
测试点30 | 运行超时 | 8628KB | 2001MS |
LOG
LUOGU
样例的思考过程
aaabcabc
ah[1] a
ah[2] aa
ah[3] aaa
ah[4] aaab
ah[5] aaabc
ah[6] aaabca
ah[7] aaabcab
ah[8] aaabcabc
1 3
aaa ah[3]-ah[0]
3 8
abcabc ah[8]-ah[2]
4 8
bcabc ah[8]-ah[3]
b ah[4]-ah[3]
bc ah[5]-ah[3]
bca ah[6]-ah[3]
bcab ah[7]-ah[3]
bcabc ah[8]-ah[3]
暴力哈希60分代码如下:
#include <cstdio>
#include <cstring>
#define B 29
#define ULL unsigned long long
#define maxn 500010
using namespace std;
char a[maxn];
int an;
ULL ah[maxn],n[maxn];
int main(){
int s,e,i,j,N,q,len,bn;
scanf("%d%s",&N,a+1);
an=strlen(a+1);
n[0]=1;
for(i=1;i<=an;i++)n[i]=n[i-1]*B;
ah[0]=0;
for(i=1;i<=an;i++)ah[i]=ah[i-1]*B+a[i]-'a'+1;
scanf("%d",&q);
while(q--){
scanf("%d%d",&s,&e);
bn=e-s+1;//区间字串总长度
for(len=1;len<=bn;len++)//循环节长度
if(bn%len==0){
for(i=s-1;i+len<=e;i+=len)
if(ah[s-1+len]-ah[s-1]*n[len]!=ah[i+len]-ah[i]*n[len])
break;
if(i==e){
printf("%d\n",len);
break;
}
}
}
return 0;
}
暴力的哈希值计算超时原因如下:
n=500000,q=2000000,每次查询a=1,b=500000
计算n=500000的因数个数m,以及循环节比较次数cnt代码如下:
#include <bits/stdc++.h>
int main(){
int n,i,m=0,cnt=0;
scanf("%d",&n);
for(i=1;i<=n;i++)
if(n%i==0)m++,cnt+=n/i;
printf("m=%d cnt=%d\n",m,cnt);
return 0;
}
上述代码对应的输入输出数据如下:
500000
m=42 cnt=1230453
n=500000,q=2000000,每次查询a=1,b=500000 极端运算次数如下:
2000000*(500000+1230453)=3.5*10^12严重超时。
2.判定循环节的O(1)的算法(60分)
若不查资料,此条判定循环节的O(1)的算法,根本想不到,这就是 省选/NOI- 需要补充的知识。
n是[l,r]这一段的循环节 的充要条件是 [l,r-n]和[l+n,r]相同(利用这个性质我们在判断是否为循环节是可以做到O(1))
上述充要条件的简单说明(网中未找到,那么自己写一份)
abcabcabcabcabcabcabc
abcabcabcabcabcabcabc
即 括号中的两字串相等,那么循环节abc成立
abc(abcabcabcabcabcabc)
(abcabcabcabcabcabc)abc
说明如下:
1abc(2abc3abc4abc5abc6abc7abc)
(1abc2abc3abc4abc5abc6abc)7abc
串1是循环节,因 括号中的两字串相等
故串2等于串1,
故串3等于串2,
故串4等于串3,
故串5等于串4,
故串6等于串5,
故串7等于串6,
故 串1是循环节。
ybt
未通过
测试点 | 结果 | 内存 | 时间 |
测试点1 | 答案正确 | 340KB | 1MS |
测试点2 | 答案正确 | 336KB | 1MS |
测试点3 | 答案正确 | 380KB | 5MS |
测试点4 | 运行超时 | 1000KB | 2002MS |
测试点5 | 运行超时 | 1388KB | 2004MS |
测试点6 | 运行超时 | 1688KB | 1998MS |
测试点7 | 运行超时 | 1828KB | 1997MS |
测试点8 | 运行超时 | 4312KB | 2001MS |
测试点9 | 运行超时 | 5308KB | 1998MS |
测试点10 | 运行超时 | 6492KB | 1997MS |
测试点11 | 答案正确 | 340KB | 1MS |
测试点12 | 答案正确 | 340KB | 1MS |
测试点13 | 答案正确 | 332KB | 1MS |
测试点14 | 答案正确 | 336KB | 1MS |
测试点15 | 答案正确 | 340KB | 1MS |
测试点16 | 答案正确 | 340KB | 1MS |
测试点17 | 答案正确 | 476KB | 77MS |
测试点18 | 答案正确 | 500KB | 125MS |
测试点19 | 答案正确 | 344KB | 1MS |
测试点20 | 答案正确 | 508KB | 42MS |
测试点21 | 答案正确 | 500KB | 132MS |
测试点22 | 答案正确 | 1996KB | 21MS |
测试点23 | 答案正确 | 480KB | 1443MS |
测试点24 | 答案正确 | 504KB | 1745MS |
测试点25 | 答案正确 | 472KB | 1214MS |
测试点26 | 答案正确 | 508KB | 1737MS |
测试点27 | 运行超时 | 8620KB | 2013MS |
测试点28 | 运行超时 | 8624KB | 1995MS |
测试点29 | 运行超时 | 8080KB | 1997MS |
测试点30 | 运行超时 | 8628KB | 2000MS |
LOJ
LUOGU
判定循环节的O(1)的算法(60分)代码如下:
#include <cstdio>
#include <cstring>
#define B 29
#define ULL unsigned long long
#define maxn 500010
using namespace std;
char a[maxn];
int an;
ULL ah[maxn],n[maxn];
int main(){
int s,e,i,j,N,q,len,bn;
scanf("%d%s",&N,a+1);
an=strlen(a+1);
n[0]=1;
for(i=1;i<=an;i++)n[i]=n[i-1]*B;
ah[0]=0;
for(i=1;i<=an;i++)ah[i]=ah[i-1]*B+a[i]-'a'+1;
scanf("%d",&q);
while(q--){
scanf("%d%d",&s,&e);
bn=e-s+1;//区间字串总长度
for(len=1;len<=bn;len++)//循环节长度
if(bn%len==0)
if(ah[e]-ah[s-1+len]*n[bn-len]==ah[e-len]-ah[s-1]*n[bn-len]){//此行代码需盯着样例才能写出
printf("%d\n",len);
break;
}
}
return 0;
}
超时说明
暴力的哈希值计算超时原因如下:
n=500000,q=2000000,每次查询a=1,b=500000
计算n=500000的因数个数m,代码如下:
#include <bits/stdc++.h>
int main(){
int n,i,m=0;
scanf("%d",&n);
for(i=1;i<=n;i++)
if(n%i==0)m++;
printf("m=%d\n",m);
return 0;
}
上述代码对应的输入输出数据如下:
500000
m=42
n=500000,q=2000000,每次查询a=1,b=500000 极端运算次数如下:
2000000*(500000+42)=1*10^12还是严重超时。
3.1线性筛 质因数分解(90分)
质因数分解
500000=(2^5)*(5^6)运输算次数5+6=11
n=500000,q=2000000,每次查询a=1,b=500000 极端运算次数如下:
2000000*11=2.2*10^7运算减轻不少。
比较好奇sqrt(500000)以内质数的个数
质数线性筛代码如下:
#include <bits/stdc++.h>
#define maxn 10000010
using namespace std;
int prime[maxn],tot,b[maxn];
void linear(int x){
int i,j;
tot=0;
for(i=2;i<=x;i++)b[i]=0;
for(i=2;i<=x;i++){
if(b[i]==0)prime[++tot]=i;
for(j=1;j<=tot&&prime[j]*i<=x;j++){
b[i*prime[j]]=1;
if(i%prime[j]==0)break;
}
}
}
int main(){
int x,i;
scanf("%d",&x);
linear(sqrt(x));
printf("tot=%d\n",tot);
for(i=1;i<=tot;i++)printf("%d ",prime[i]);
return 0;
}
上述代码对应的输入输出数据如下:
500000
tot=126
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113
127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 229 233 239 241
251 257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349 353 359 367 373 379 383
389 397 401 409 419 421 431 433 439 443 449 457 461 463 467 479 487 491 499 503 509 521 523
541 547 557 563 569 571 577 587 593 599 601 607 613 617 619 631 641 643 647 653 659 661 673
677 683 691 701
检验线性筛正确与否的标准,在1-100这个区间内,有25个质数。
进一步说明 n=500000,q=2000000,每次查询a=1,b=500000 极端运算次数如下:
sqrt(500000)内有126个质数,500000=2^19,可尝试126个质数,每个质数至多尝试19次,
计算次数2000000*126*19=5*10^9,明显优化了不少,会不会超时,那就要看数据了。
ybt
未通过
测试点 | 结果 | 内存 | 时间 |
测试点1 | 答案正确 | 408KB | 1MS |
测试点2 | 答案正确 | 404KB | 1MS |
测试点3 | 答案正确 | 448KB | 3MS |
测试点4 | 答案正确 | 1068KB | 43MS |
测试点5 | 答案错误 | 1452KB | 58MS |
测试点6 | 答案错误 | 1756KB | 52MS |
测试点7 | 答案正确 | 1896KB | 54MS |
测试点8 | 答案正确 | 4392KB | 379MS |
测试点9 | 答案正确 | 5376KB | 462MS |
测试点10 | 答案正确 | 6552KB | 803MS |
测试点11 | 答案正确 | 400KB | 2MS |
测试点12 | 答案正确 | 400KB | 1MS |
测试点13 | 答案正确 | 404KB | 1MS |
测试点14 | 答案正确 | 400KB | 1MS |
测试点15 | 答案正确 | 404KB | 1MS |
测试点16 | 答案正确 | 396KB | 1MS |
测试点17 | 答案正确 | 544KB | 5MS |
测试点18 | 答案正确 | 564KB | 5MS |
测试点19 | 答案正确 | 412KB | 1MS |
测试点20 | 答案正确 | 564KB | 5MS |
测试点21 | 答案正确 | 568KB | 5MS |
测试点22 | 答案正确 | 2068KB | 20MS |
测试点23 | 答案错误 | 544KB | 38MS |
测试点24 | 答案错误 | 568KB | 43MS |
测试点25 | 答案错误 | 528KB | 41MS |
测试点26 | 答案错误 | 572KB | 43MS |
测试点27 | 答案错误 | 8696KB | 863MS |
测试点28 | 答案正确 | 8696KB | 1707MS |
测试点29 | 答案正确 | 8144KB | 830MS |
测试点30 | 答案正确 | 8688KB | 1559MS |
LOJ
LUOGU
线性筛 质因数分解(90分)代码如下:
#include <cstdio>
#include <cstring>
#include <cmath>
#define B 29
#define ULL unsigned long long
#define maxn 500010
using namespace std;
char a[maxn];
int an;
ULL ah[maxn],n[maxn];
int prime[maxn],tot,b[maxn];
void linear(int x){
int i,j;
tot=0;
for(i=2;i<=x;i++)b[i]=0;
for(i=2;i<=x;i++){
if(b[i]==0)prime[++tot]=i;
for(j=1;j<=tot&&prime[j]*i<=x;j++){
b[i*prime[j]]=1;
if(i%prime[j]==0)break;
}
}
}
int main(){
int s,e,i,N,q,len,bn;
scanf("%d%s",&N,a+1);
an=strlen(a+1);
n[0]=1;
for(i=1;i<=an;i++)n[i]=n[i-1]*B;
ah[0]=0;
for(i=1;i<=an;i++)ah[i]=ah[i-1]*B+a[i]-'a'+1;
linear(sqrt(an));//线性筛
scanf("%d",&q);
while(q--){
scanf("%d%d",&s,&e);
bn=e-s+1;//区间字串总长度
len=1;
if(ah[e]-ah[s-1+len]*n[bn-len]==ah[e-len]-ah[s-1]*n[bn-len]){//len=1作为特判
printf("%d\n",len);//这个特判是在测试1 3这个数据中发现的,特判能省去不少麻烦
continue;
}
len=bn;
for(i=tot;i>=1;i--)
while(len%prime[i]==0&&ah[e]-ah[s-1+len/prime[i]]*n[bn-len/prime[i]]==ah[e-len/prime[i]]-ah[s-1]*n[bn-len/prime[i]])
len=len/prime[i];
printf("%d\n",len);
}
return 0;
}
3.2线性筛 质因数分解(70分)
3.1中忽略的地方
看到了3372=2*2*3*281请注意281是质数,但不在[1,sqrt(3372)]区间范围之内。
即将linear(sqrt(an));改成linear(an);
分数降低到了70分。
ybt
未通过
测试点 | 结果 | 内存 | 时间 |
测试点1 | 答案正确 | 344KB | 1MS |
测试点2 | 答案正确 | 348KB | 1MS |
测试点3 | 答案正确 | 396KB | 5MS |
测试点4 | 答案正确 | 1180KB | 1013MS |
测试点5 | 运行超时 | 1676KB | 2001MS |
测试点6 | 运行超时 | 2036KB | 2000MS |
测试点7 | 运行超时 | 2212KB | 1997MS |
测试点8 | 运行超时 | 5340KB | 1996MS |
测试点9 | 运行超时 | 6572KB | 2000MS |
测试点10 | 运行超时 | 8064KB | 1996MS |
测试点11 | 答案正确 | 340KB | 0MS |
测试点12 | 答案正确 | 348KB | 1MS |
测试点13 | 答案正确 | 336KB | 1MS |
测试点14 | 答案正确 | 344KB | 1MS |
测试点15 | 答案正确 | 340KB | 1MS |
测试点16 | 答案正确 | 340KB | 1MS |
测试点17 | 答案正确 | 512KB | 36MS |
测试点18 | 答案正确 | 544KB | 34MS |
测试点19 | 答案正确 | 344KB | 1MS |
测试点20 | 答案正确 | 544KB | 39MS |
测试点21 | 答案正确 | 548KB | 35MS |
测试点22 | 答案正确 | 2424KB | 23MS |
测试点23 | 答案正确 | 528KB | 325MS |
测试点24 | 答案正确 | 548KB | 412MS |
测试点25 | 答案正确 | 500KB | 322MS |
测试点26 | 答案正确 | 556KB | 405MS |
测试点27 | 运行超时 | 10740KB | 1998MS |
测试点28 | 运行超时 | 10740KB | 1991MS |
测试点29 | 运行超时 | 10056KB | 2004MS |
测试点30 | 运行超时 | 10732KB | 1997MS |
LOJ
LUOGU
线性筛 质因数分解(70分)代码如下:
#include <cstdio>
#include <cstring>
#include <cmath>
#define B 29
#define ULL unsigned long long
#define maxn 500010
using namespace std;
char a[maxn];
int an;
ULL ah[maxn],n[maxn];
int prime[maxn],tot,b[maxn];
void linear(int x){
int i,j;
tot=0;
for(i=2;i<=x;i++)b[i]=0;
for(i=2;i<=x;i++){
if(b[i]==0)prime[++tot]=i;
for(j=1;j<=tot&&prime[j]*i<=x;j++){
b[i*prime[j]]=1;
if(i%prime[j]==0)break;
}
}
}
int main(){
int s,e,i,N,q,len,bn;
scanf("%d%s",&N,a+1);
an=strlen(a+1);
n[0]=1;
for(i=1;i<=an;i++)n[i]=n[i-1]*B;
ah[0]=0;
for(i=1;i<=an;i++)ah[i]=ah[i-1]*B+a[i]-'a'+1;
linear(an);//线性筛 注意,此处不能写成linear(sqrt(an));
scanf("%d",&q);
while(q--){
scanf("%d%d",&s,&e);
bn=e-s+1;//区间字串总长度
len=1;
if(ah[e]-ah[s-1+len]*n[bn-len]==ah[e-len]-ah[s-1]*n[bn-len]){//len=1作为特判
printf("%d\n",len);//这个特判是在测试1 3这个数据中发现的,特判能省去不少麻烦
continue;
}
len=bn;
for(i=tot;i>=1;i--)
while(len%prime[i]==0&&ah[e]-ah[s-1+len/prime[i]]*n[bn-len/prime[i]]==ah[e-len/prime[i]]-ah[s-1]*n[bn-len/prime[i]])
len=len/prime[i];
printf("%d\n",len);
}
return 0;
}
3.3线性筛 质因数分解(100分)
核心在于存储长度的质因数质因数
1、循环节一定是长度的约数
2、如果n是一个循环节,那么k*n也必定是一个循环节(关键所在)
3、n是[l,r]这一段的循环节 的充要条件是 [l,r-n]和[l+n,r]相同(利用这个性质我们在判断是否为循环节是可以做到O(1))
所以我们可以在求出这个区间的长度之后,判断它的每个约数是否是循环节(应用性质3),并且因为性质1,它的约数是循环节,原串一定也是。
所以只要不断除以质因数(相当于从大到小枚举约数),缩小L的长度,最后就是最小的长度。
ybt
通过
测试点 | 结果 | 内存 | 时间 |
测试点1 | 答案正确 | 396KB | 0MS |
测试点2 | 答案正确 | 396KB | 1MS |
测试点3 | 答案正确 | 440KB | 2MS |
测试点4 | 答案正确 | 1064KB | 39MS |
测试点5 | 答案正确 | 1444KB | 52MS |
测试点6 | 答案正确 | 1740KB | 39MS |
测试点7 | 答案正确 | 1900KB | 40MS |
测试点8 | 答案正确 | 4380KB | 251MS |
测试点9 | 答案正确 | 5364KB | 311MS |
测试点10 | 答案正确 | 6540KB | 505MS |
测试点11 | 答案正确 | 400KB | 2MS |
测试点12 | 答案正确 | 388KB | 1MS |
测试点13 | 答案正确 | 404KB | 1MS |
测试点14 | 答案正确 | 388KB | 1MS |
测试点15 | 答案正确 | 388KB | 1MS |
测试点16 | 答案正确 | 396KB | 1MS |
测试点17 | 答案正确 | 528KB | 5MS |
测试点18 | 答案正确 | 572KB | 5MS |
测试点19 | 答案正确 | 392KB | 1MS |
测试点20 | 答案正确 | 556KB | 5MS |
测试点21 | 答案正确 | 572KB | 5MS |
测试点22 | 答案正确 | 2052KB | 28MS |
测试点23 | 答案正确 | 548KB | 37MS |
测试点24 | 答案正确 | 564KB | 41MS |
测试点25 | 答案正确 | 516KB | 40MS |
测试点26 | 答案正确 | 560KB | 42MS |
测试点27 | 答案正确 | 8692KB | 533MS |
测试点28 | 答案正确 | 8692KB | 1039MS |
测试点29 | 答案正确 | 8136KB | 529MS |
测试点30 | 答案正确 | 8692KB | 1047MS |
LOJ
LUOGU
线性筛 质因数分解(100分)代码如下:
#include <cstdio>
#include <cstring>
#include <cmath>
#define B 29
#define ULL unsigned long long
#define maxn 500010
using namespace std;
char a[maxn];
int an;
ULL ah[maxn],n[maxn];
int prime[maxn],tot,b[maxn],p[maxn],pn;
void linear(int x){
int i,j;
tot=0;
for(i=2;i<=x;i++)b[i]=0;
for(i=2;i<=x;i++){
if(b[i]==0)prime[++tot]=i;
for(j=1;j<=tot&&prime[j]*i<=x;j++){
b[i*prime[j]]=1;
if(i%prime[j]==0)break;
}
}
}
int main(){
int s,e,i,N,q,len,bn;
scanf("%d%s",&N,a+1);
an=strlen(a+1);
n[0]=1;
for(i=1;i<=an;i++)n[i]=n[i-1]*B;
ah[0]=0;
for(i=1;i<=an;i++)ah[i]=ah[i-1]*B+a[i]-'a'+1;
linear(sqrt(an));//线性筛
scanf("%d",&q);
while(q--){
scanf("%d%d",&s,&e);
bn=e-s+1;//区间字串总长度
len=bn;
pn=0;
for(i=1;i<=tot&&prime[i]*prime[i]<=len;i++){//找出bn的所有质因数
if(len%prime[i]==0){
p[++pn]=prime[i];
while(len%prime[i]==0)len/=prime[i];
}
}
if(len>1)p[++pn]=len;//不在[1,sqrt(bn)]范畴内的质数
len=1;//特判,循环节是1个字符
if(ah[e]-ah[s-1+len]*n[bn-len]==ah[e-len]-ah[s-1]*n[bn-len]){//len=1作为特判
printf("%d\n",len);//这个特判是在测试1 3这个数据中发现的,特判能省去不少麻烦
continue;
}
len=bn;
for(i=pn;i>=1;i--)
while(len%p[i]==0&&ah[e]-ah[s-1+len/p[i]]*n[bn-len/p[i]]==ah[e-len/p[i]]-ah[s-1]*n[bn-len/p[i]])
len=len/p[i];
printf("%d\n",len);
}
return 0;
}
本以为半天就能拿下,结果断断续续,耗时3个半天。
该题编写,借鉴过他人思想,但他人代码是一点都没看过。
省选/NOI- 这个级别,总有些该学习的内容。
若不查资料,此条判定循环节的O(1)的算法,根本想不到,这就是 省选/NOI- 需要补充的知识。
n是[l,r]这一段的循环节 的充要条件是 [l,r-n]和[l+n,r]相同(利用这个性质我们在判断是否为循环节是可以做到O(1))
上述充要条件的简单说明(网中未找到,那么自己写一份)
abcabcabcabcabcabcabc
abcabcabcabcabcabcabc
即 括号中的两字串相等,那么循环节abc成立
abc(abcabcabcabcabcabc)
(abcabcabcabcabcabc)abc
说明如下:
1abc(2abc3abc4abc5abc6abc7abc)
(1abc2abc3abc4abc5abc6abc)7abc
串1是循环节,因 括号中的两字串相等
故串2等于串1,
故串3等于串2,
故串4等于串3,
故串5等于串4,
故串6等于串5,
故串7等于串6,
故 串1是循环节。