1460 Poi2012 A Horrible Poem(Bzoj2795 LOJ10038 LUOGU3538 省选/NOI-) 暴力哈希60分 循环节判定(60分)说明 线性筛 质因数分解100分

总目录

在线测评地址(ybt)

在线测评地址(LOJ)

在线测评地址(LUOGU)

1.暴力哈希(60分)

ybt

未通过

测试点结果内存时间
测试点1答案正确336KB0MS
测试点2答案正确332KB1MS
测试点3答案正确388KB4MS
测试点4运行超时992KB2006MS
测试点5运行超时1392KB2014MS
测试点6运行超时1688KB2004MS
测试点7运行超时1824KB1995MS
测试点8运行超时4320KB1998MS
测试点9运行超时5304KB1998MS
测试点10运行超时6484KB1998MS
测试点11答案正确344KB1MS
测试点12答案正确344KB2MS
测试点13答案正确344KB2MS
测试点14答案正确332KB2MS
测试点15答案正确344KB1MS
测试点16答案正确336KB2MS
测试点17答案正确476KB127MS
测试点18答案正确512KB227MS
测试点19答案正确348KB0MS
测试点20答案正确500KB67MS
测试点21答案正确500KB223MS
测试点22运行超时1992KB2003MS
测试点23答案正确492KB1618MS
测试点24运行超时504KB1991MS
测试点25答案正确464KB1396MS
测试点26答案正确508KB1967MS
测试点27运行超时8628KB1997MS
测试点28运行超时8624KB1995MS
测试点29运行超时8084KB1998MS
测试点30运行超时8628KB2001MS

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答案正确340KB1MS
测试点2答案正确336KB1MS
测试点3答案正确380KB5MS
测试点4运行超时1000KB2002MS
测试点5运行超时1388KB2004MS
测试点6运行超时1688KB1998MS
测试点7运行超时1828KB1997MS
测试点8运行超时4312KB2001MS
测试点9运行超时5308KB1998MS
测试点10运行超时6492KB1997MS
测试点11答案正确340KB1MS
测试点12答案正确340KB1MS
测试点13答案正确332KB1MS
测试点14答案正确336KB1MS
测试点15答案正确340KB1MS
测试点16答案正确340KB1MS
测试点17答案正确476KB77MS
测试点18答案正确500KB125MS
测试点19答案正确344KB1MS
测试点20答案正确508KB42MS
测试点21答案正确500KB132MS
测试点22答案正确1996KB21MS
测试点23答案正确480KB1443MS
测试点24答案正确504KB1745MS
测试点25答案正确472KB1214MS
测试点26答案正确508KB1737MS
测试点27运行超时8620KB2013MS
测试点28运行超时8624KB1995MS
测试点29运行超时8080KB1997MS
测试点30运行超时8628KB2000MS

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答案正确408KB1MS
测试点2答案正确404KB1MS
测试点3答案正确448KB3MS
测试点4答案正确1068KB43MS
测试点5答案错误1452KB58MS
测试点6答案错误1756KB52MS
测试点7答案正确1896KB54MS
测试点8答案正确4392KB379MS
测试点9答案正确5376KB462MS
测试点10答案正确6552KB803MS
测试点11答案正确400KB2MS
测试点12答案正确400KB1MS
测试点13答案正确404KB1MS
测试点14答案正确400KB1MS
测试点15答案正确404KB1MS
测试点16答案正确396KB1MS
测试点17答案正确544KB5MS
测试点18答案正确564KB5MS
测试点19答案正确412KB1MS
测试点20答案正确564KB5MS
测试点21答案正确568KB5MS
测试点22答案正确2068KB20MS
测试点23答案错误544KB38MS
测试点24答案错误568KB43MS
测试点25答案错误528KB41MS
测试点26答案错误572KB43MS
测试点27答案错误8696KB863MS
测试点28答案正确8696KB1707MS
测试点29答案正确8144KB830MS
测试点30答案正确8688KB1559MS

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答案正确344KB1MS
测试点2答案正确348KB1MS
测试点3答案正确396KB5MS
测试点4答案正确1180KB1013MS
测试点5运行超时1676KB2001MS
测试点6运行超时2036KB2000MS
测试点7运行超时2212KB1997MS
测试点8运行超时5340KB1996MS
测试点9运行超时6572KB2000MS
测试点10运行超时8064KB1996MS
测试点11答案正确340KB0MS
测试点12答案正确348KB1MS
测试点13答案正确336KB1MS
测试点14答案正确344KB1MS
测试点15答案正确340KB1MS
测试点16答案正确340KB1MS
测试点17答案正确512KB36MS
测试点18答案正确544KB34MS
测试点19答案正确344KB1MS
测试点20答案正确544KB39MS
测试点21答案正确548KB35MS
测试点22答案正确2424KB23MS
测试点23答案正确528KB325MS
测试点24答案正确548KB412MS
测试点25答案正确500KB322MS
测试点26答案正确556KB405MS
测试点27运行超时10740KB1998MS
测试点28运行超时10740KB1991MS
测试点29运行超时10056KB2004MS
测试点30运行超时10732KB1997MS

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答案正确396KB0MS
测试点2答案正确396KB1MS
测试点3答案正确440KB2MS
测试点4答案正确1064KB39MS
测试点5答案正确1444KB52MS
测试点6答案正确1740KB39MS
测试点7答案正确1900KB40MS
测试点8答案正确4380KB251MS
测试点9答案正确5364KB311MS
测试点10答案正确6540KB505MS
测试点11答案正确400KB2MS
测试点12答案正确388KB1MS
测试点13答案正确404KB1MS
测试点14答案正确388KB1MS
测试点15答案正确388KB1MS
测试点16答案正确396KB1MS
测试点17答案正确528KB5MS
测试点18答案正确572KB5MS
测试点19答案正确392KB1MS
测试点20答案正确556KB5MS
测试点21答案正确572KB5MS
测试点22答案正确2052KB28MS
测试点23答案正确548KB37MS
测试点24答案正确564KB41MS
测试点25答案正确516KB40MS
测试点26答案正确560KB42MS
测试点27答案正确8692KB533MS
测试点28答案正确8692KB1039MS
测试点29答案正确8136KB529MS
测试点30答案正确8692KB1047MS

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是循环节。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值