题目描述
两个士兵正在玩一个游戏,游戏开始的时候,第一个士兵为第二个士兵选一个正整数n。然后第二个士兵要玩尽可能多的轮数。每一轮要选择一个正整数x>1,且n要是x的倍数,然后用n/x去代替n。当n变成1的时候,游戏就结束了,第二个士兵所得的分数就是他玩游戏的轮数。
为了使游戏更加有趣,第一个士兵用 a! / b! 来表示n。k!表示把所有1到k的数字乘起来。
那么第二个士兵所能得到的最大分数是多少呢?
问题转化
因为要玩尽可能多的轮数,所以第二个士兵肯定是每次选最小的质因子,这样n变化的幅度最小,也就最慢结束游戏。
于是,题目就转化成了: 求a! / b! 中每一个数的质因子个数之和。
这时候,就要搬出我们的唯一分解定理了。
定理本理:
任何一个大于1的自然数 N ,如果N不为质数,都可以唯一分解成有限个质数的乘积 N = P 1 a 1 P 2 a 2 . . . P n a n N\ = P_1^{a_1} P_2^{a_2}...P_n^{a_n} N =P1a1P2a2...Pnan ,这里 P 1 < P 2 < . . . < P 3 P_1<P_2<...<P_3 P1<P2<...<P3 均为质数,其诸指数 a i a_i ai是正整数。
这样的分解也被称为N的标准分解式。
所以,假如
a!可以被表示成
a
!
=
2
a
1
⋅
3
a
2
⋅
5
a
3
.
.
.
⋅
P
a
n
a!=2^{a_1}·3^{a_2}·5^{a_3}...·P^{a_n}
a!=2a1⋅3a2⋅5a3...⋅Pan,
b!可以被表示成
b
!
=
2
b
1
⋅
3
b
2
⋅
5
b
3
.
.
.
⋅
P
b
n
b!=2^{b_1}·3^{b_2}·5^{b_3}...·P^{b_n}
b!=2b1⋅3b2⋅5b3...⋅Pbn
那么a! / b!就可以被表示为
a
!
/
b
!
=
2
a
1
−
b
1
⋅
3
a
2
−
b
2
⋅
5
a
3
−
b
3
.
.
.
⋅
P
a
n
−
b
n
a! / b!=2^{a_1-b_1}·3^{a_2-b_2}·5^{a_3-b_3}...·P^{a_n-b_n}
a!/b!=2a1−b1⋅3a2−b2⋅5a3−b3...⋅Pan−bn
归根结底,我们就是要求n!中每一个数的质因子个数之和sum[n],然后用sum[a]-sum[b]即可得到答案。
为了统计sum[i],我们可以先算出每一个数的质因子个数f[i],最后用一个前缀和统计就可以了。
选择筛法
筛素数我们很会,前缀和我们也很懂,问题是怎么把筛素数和计算一个数的质因子个数在不超时的情况下完成?
我们想到了(或是抄到的 )欧拉筛!!!
虽然它不是最快的筛法(百度了一下,最快的好像是什么数域筛法),但它可以保证一个数只被筛一次,并且我们知道它是被谁筛掉的。这一点很重要,因为知道它是被谁筛的就可以计算它的质因子个数!!!
为什么欧拉筛可以保证一个数只被筛一次?
因为存素数的prime[]数组中的素数是递增的,当i能被prime[j]整除时,i* prime[j+1]这个合数肯定被prime[j]乘以某个数筛掉。
因为i中含有prime[j],prime[j]比prime[j+1]小,即i=k* prime[j],那么i* prime[j+1]=(k*prime[j])*prime[j+1]=k’*prime[j]。
剩下的素数同理,所以不用再筛下去了。因此,在i%prime[j]==0这个条件之前以及第一次满足该条件时,prime[j]必定是prime[j]*i的最小因子。
最后一步:计算f[i]。
既然i只会被筛一次,那么它的质因子个数就是筛掉它的prime[j]和k的质因子个数之和啦!
为什么是和?
这个也跟唯一分解定理挂钩。举个栗子通俗易懂。
12=2*6(12会被它最小的质因子2筛掉)
我们把等式分解成它的标准分解式:
2
2
∗
3
1
=
2
1
∗
2
1
∗
3
1
2^2*3^1=2^1*2^1*3^1
22∗31=21∗21∗31
发现了什么没有?左边可的质因数的指数=右边的质因数的指数相加,而我们算的f[i]恰好就是指数之和,所以是加起来。
那么代码也就很好写啦~
#include<cstdio>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;
int t,a,b;
int cnt,prime[5100000],f[5100000],sum[5100000];
bool v[5100000];
int read()//加了个快读
{
int ret=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9'){f=-f;ch=getchar();}
while(ch>='0' && ch<='9'){ret=ret*10+(int)(ch-'0');ch=getchar();}
return ret*f;
}
void prime_(int n)
{
cnt=0;
memset(v,true,sizeof(v));
v[0]=v[1]=false;
for(int i=2 ; i<=n ; i++)
{
if(v[i]==true)prime[++cnt]=i,f[i]=1;
for(int j=1 ; j<=cnt && prime[j]*i<=n ; j++)
{
v[i*prime[j]]=false;
f[i*prime[j]]=f[i]+f[prime[j]];//重点!!!
if(i%prime[j]==0)break;
}
}
sum[1]=0;sum[2]=1;
for(int i=3 ; i<=n ; i++)sum[i]=sum[i-1]+f[i];
}
signed main()
{
prime_(5000000);
scanf("%d",&t);
while(t--)
{
scanf("%d %d",&a,&b);
printf("%d\n",sum[a]-sum[b]);
}
return 0;
}
完结撒花~