#A. 求约数个数之一
- 传统题1000ms256MiB
Description
输入一个数字,输出其约数个数。例如10就有4个约数1,2,5,10
Format
Input
一个数字N,N< =1000
Output
如题
Samples
输入数据 1
10
输出数据 1
4
Limitation
1s, 1024KiB for each test case.
数据范围很小,枚举2-n即可。
/*
version 1.0
*/
#include<bits/stdc++.h>
using namespace std;
int main()
{
long long n;
int sum=0;
cin>>n;
for (int i=1;i<=n;i++)
if (n%i==0)
sum++;
cout<<sum<<endl;
return 0;
}
时间复杂度O(N)
#B. 求约数个数之二
- 传统题1000ms256MiB
同上一题,只是输入数字N小于等于2147483647
n>10^8,A题做法会超时。
容易想到因子是成对出现的:若m为n的因数,那么n/m也是
如对于12:1是12的因数,那么12/1=12也是;2是12的因数,那么12/2=6也是;3是12的因数,那么12/4=12也是。
值得注意的是,对于完全平方数如100,因数10只算一遍,需要特判。
/*
version 2.0
*/
#include<bits/stdc++.h>
using namespace std;
int n,ans;
int main()
{
scanf("%d",&n);
for(int i=1;i<=sqrt(n);i++)
if(n%i==0)
if(i*i==n)
{
ans++;
break;
}
else
ans+=2;
printf("%d",ans);
return 0;
}
时间复杂度O(根号N)
#C. 求约数个数之三
- 传统题1000ms256MiB
Description
输入一个数字,输出其约数个数。例如10就有4个约数1,2,5,10
Format
Input
一个数字N,N< =2^63-1
Output
如题
Samples
输入数据 1
10
输出数据 1
4
N越来越大了,不能再用枚举。
原理:一个数及其因子都可以由其质因子相乘得到。
例如;12=2^2*3^1,12的质因子为2,3;因子为1,2,3,4,6,12。
则有
1=2^0 * 3^0
2=2^1 * 3^0
3=2^0 * 3^1
4=2^2 * 3^0
6=2^1 * 3^1
12=2^2 * 3^1
由乘法原理,12的因子数为(2+1)*(1+1)=6。
也就是:
把一个数N写成:N=(p1^x1)(p^x2)(p3^x3)…(pi^xi)(pi为质数),则N的因数个数为(x1+1)(x2+1)(x3+1)…(xk+1)。
/*
version 3.0
*/
#include<bits/stdc++.h>
using namespace std;
long long n,ans=1,k;
int main()
{
scanf("%lld",&n);
for(int i=2;i<=sqrt(n);i++)
{
k=0;
while(n%i==0)
{
k++;
n/=i;
}
ans*=k+1;
if(n==1)
break;
}
if(n!=1)
ans*=2;
printf("%d",ans);
return 0;
}
可以反证,若n%i=0,则i为质数。
若i为合数,那在之前的循环中,i作为其因子的若干倍,已经被筛掉。
#D. 求约数个数之四之求N!的因子数
- 传统题1000ms256MiB
Description
求N!的因子数%10^9+7
1≤N≤1000000
Format
Input
一行给出数字N
N<=1000000
Output
如题
Samples
输入数据 1
3
输出数据 1
4
输入数据 2
100
输出数据 2
583951250
13!超过int的范围,20!超过unsigned long long的范围
所以n!肯定不能直接算出来
n!=1*2*……*(n-1)*n,我们可以退而处理1-n
沿用C题的思路,运用加法原理
例如6!=1*2*3*4*5*6。
2=2^1
3=3^1
4=2^2
5=5^1
6=2^1*3^1
所以6!=2^4*3^2*5^1
所以6!的因子数为(4+1)(2+1)(1+1)=30
/*
version 4.0
*/
#include<bits/stdc++.h>
using namespace std;
int n,prime[1000002],tot,num[1000002],MOD=1000000007;
long long ans=1,k;
int main()
{
scanf("%d",&n);
for(int i=2;i<=n;i++)
if(num[i]==0)
{
tot++;
prime[tot]=i;//筛出n以内的质数
for(int j=2;j<=n/i;j++)
num[j*i]=1;
}
for(int i=1;i<=n;i++)
num[i]=i;
for(int i=1;i<=tot;i++)
{
k=0;
for(int j=1;j<=n;j++)
while(num[j]%prime[i]==0)
{
k++;
num[j]/=prime[i];
}
k++;
ans=ans*k%MOD;
}
for(int i=1;i<=n;i++)
if(num[i]!=1)
ans=ans*2%MOD;
printf("%lld",ans);
return 0;
}
TLE,还是不够快
如何优化?
我们发现上面这个程序是对2-n做质因子分解,看是否能分出质因子出来。
如果变换一个思路,我们能否成批成批的找出那些一定包含5这个质因子的数字呢?
我们将这些数字进行分类考虑
只能分解出来1个5出来的,则它们可统一写成5*x这样的形式
于是当N = 1000时:
5*x<=1000,x<=200
即找出来的数字形如5,10,15,20,25,30,35,40,45,50这样形式
能分解出来2个5出来的,则它们可统一写成25 * x这样的形式
于是25*x<=1000,x<=40
即找出来的数字形如25,50,75,100这样形式,发现这些数字在前面已找出过一次了。因为一个数字能写成25 * x这样的形式,也必然可写成5 * x这样的形式。于是这些数字虽然能提供2个5出来,但在本次计算中只能累加一次。
能分解出来3个5出来的,则它们可统一写成125 * x这样的形式
125*x<=1000,x<=8
能分解出来4个5出来的,则它们可统一写成625 * x这样的形式
625*x<=1000,x<=1
能分解出来5个5出来的,则它们可统一写成3125 * x这样的形式
3125*x<=1000,x <=0
于是当N=1000时,积累可提供200+40+8+1=249个5
/*
version 5.0
*/
#include<bits/stdc++.h>
using namespace std;
int n,prime[1000002],tot,num[1000002],MOD=1000000007;
long long ans=1,k,ii,nn;
int main()
{
scanf("%d",&n);
for(int i=2;i<=n;i++)
if(num[i]==0)
{
tot++;
prime[tot]=i;
for(int j=2;j<=n/i;j++)
num[j*i]=1;
}
for(int i=1;i<=n;i++)
num[i]=i;
for(int i=1;i<=tot;i++)
{
k=0;
ii=prime[i];
nn=n;
while(nn>=ii)
{
k+=nn/ii;
ii*=prime[i];
}
ans=ans*(k+1)%MOD;
}
printf("%lld",ans);
return 0;
}
总结如下
version 1.0 属于无脑枚举,好想好写,但只适用于小数据
version 2.0 依托因子成对的性质,也是枚举,但时间复杂度从O(N)降到O(根号N)
version 3.0 依托质因子分解+乘法原理的数学知识,免去很多循环
version 4.0 是3.0+加法原理
version 5.0 免去枚举1-n的数字,批量找出含质因子数字的个数