MT2203 约数个数
难度:黄金 时间限制:1秒 占用内存:128M
题目描述
给定正整数 n n n,求 n n n 的约数个数。
格式
输入格式:一个整数 n n n。
输出格式:输出一行一个整数表示答案。样例 1
输入:
12输出:
6备注
其中: 1 ≤ n ≤ 1 0 9 1\le n \le 10^9 1≤n≤109。
相关知识点:
数论
方法一:暴力求解
求数 n n n 的约数(因数)个数,最简单的办法就是通过一重循环扫描 1 ∼ n 1\sim n 1∼n 并逐个取余,然后统计所有余数为 0 的情况即可。实际中为了加快扫描速度,通常只会扫描 1 ∼ n 1\sim \sqrt n 1∼n,然后在遇到余数为 0 的情况时统计 2 个单位的数。因为对任何数 n n n,如果存在 n m o d x = 0 n\mod x=0 nmodx=0,则必定有 n m o d n x = 0 n\mod\frac{n}{x}=0 nmodxn=0 成立。例如,对 12 而言,当 12 m o d 2 = 0 12\mod 2=0 12mod2=0 时,同时有 12 m o d 12 2 = 12 m o d 6 = 0 12\mod\frac{12}{2}=12\mod6=0 12mod212=12mod6=0 。采取这种方法求某个数( n n n)的余数个数时,对 n \sqrt n n 需要注意一点:当 n \sqrt n n 为 n n n 的约数且 n \sqrt n n 为整数时,统计时只计 1 个单位的数。例如,对于数 25,其平方根为 25 = 5 \sqrt{25}=5 25=5,由于 25 5 = 5 \frac{25}{5}=5 525=5 ,还是 5 这个数本身,因此这里实际上只产生一个约数,所以只需要计 1 个数。采取这种方法得到的代码如下:
// 求指定数的约数个数
int getDivisorNum(int n)
{
int divNum = 2, limit = sqrt(n);
// 统计约数个数
for(int i=2; i<=limit; i++)
if(n%i == 0)
divNum += 2;
// 特判
if(limit*limit == n) divNum --;
return divNum;
}
方法二:分解质因数
我们知道,任何一个合数都可以写成若干个质数相乘的形式,其中每个质数都是这个合数的因数,把一个合数用质因数相乘的形式表示出来,叫做分解质因数,也叫做分解质因子。
初中时,我们学过通过短除法的形式来分解一个数的质因数,其过程如下:
因此,可将360用质因数表达为:
360 = 2 3 × 3 2 × 5 1 360=2^3\times3^2\times5^1 360=23×32×51
也就是说,从 2 i 、 3 j 、 5 k 2^i、3^j、5^k 2i、3j、5k 中任意选择(注: i ∈ { 0 , 1 , 2 , 3 } , j ∈ { 0 , 1 , 2 } , k ∈ { 0 , 1 } i\in\left\{0,\ 1,\ 2,\ 3\right\},j\in\left\{0,\ 1,\ 2\right\},k\in\left\{0,\ 1\right\} i∈{0, 1, 2, 3},j∈{0, 1, 2},k∈{0, 1}),并将选出的数相乘得到的数都是 360 的因数。例如: 2 2 × 3 0 × 5 0 = 4 、 2 1 × 3 1 × 5 0 = 6 、 2 1 × 3 1 × 5 1 = 30 、 2 0 × 3 2 × 5 1 = 45 2^2\times3^0\times5^0=4、2^1\times3^1\times5^0=6、2^1\times3^1\times5^1=30、2^0\times3^2\times5^1=45 22×30×50=4、21×31×50=6、21×31×51=30、20×32×51=45 都可以被 360 整除。
那由这些数可以构成多少种选数组合呢?这是一个排列组合问题:第 λ \lambda λ 个集合中有 x λ x_\lambda xλ 种取法,问总的取法有多少种?答案是 ∏ λ x λ \prod_{\lambda} x_\lambda ∏λxλ 种。例如,上面的例子中,总的组合方案数为:
∏ λ = 1 3 x λ = 4 × 3 × 2 = 24 \prod_{\lambda=1}^{3}x_\lambda=4\times3\times2=24 λ=1∏3xλ=4×3×2=24
所以现在的我们的任务就是如何分解质因数,并统计各质因数的次数?
这个任务很简单,我们只需要模拟短除法即可,具体实现如下:
// 通过短除法获取一个数的质因数
void shortDivision(int n){
// 保存全部的质因数
queue<int> q;
for(int i=2; i*i<=n; i++){
// 当前数为数 n 的因数时,要不断用该数进行分解
while(n%i==0){
// 加入队列
q.push(i);
// 对数 n 进行分解
n /= i;
}
}
// 如果分解得到的最后结果不为 1 ,则最终状态的 n 也是原数的因数
if(n!=1) q.push(n);
}
在上面的代码中,系统会从最小的质因数 2 开始向后遍历,一旦确定某个数 i i i 是传入的数 n n n 的质因数后,会通过一层循环不断地对原数 n n n 按 i i i 进行分解。例如,当 n = 360 n=360 n=360 时,会对 i = 2 i=2 i=2 执行以下步骤:
这个过程不仅统计了质因数 i i i 的次数,还对外层循环进行了缩减(从360→180→90→45)。
接下来由于 45%2≠0 ,故退出 while 循环,并将 i i i 从 2 变为 3( 3 × 3 = 9 < 45 3\times3=9<45 3×3=9<45),接着执行以下步骤:
接下来由于 5%3≠0,故 i i i 从 3 变为 4。但对外层循环控制条件而言,由于 4 × 4 = 16 > 5 4\times4=16>5 4×4=16>5,故退出。
此时观察原数 n n n 的最终取值,由于该值不为 1,说明这个数也是原数 n n n (360)的一个质因数,因此将其加入队列中(此时队列 q={2, 2, 2, 3, 3, 5})。
最终算法结束,队列 q 中保存的即为给定数 n n n 的全部质因数。
对本题而言,我们不需要单独设置数据结构去存储全部质因数,而只需要统计每一个质因数的出现次数,并将这些次数进行叠乘即可。因此,可得到求解本题的完整代码:
/*
MT2203 约数个数
*/
#include
using namespace std;
// 求指定数的约数个数(利用短除法求出所有质约数)
int getDivisorNum(int n)
{
int divSum = 1, tmp;
for(int i=2; i*i<=n; i++){
tmp = 1;
// 统计质因数个数
while(n%i==0){
tmp++;
n /= i;
}
// 对约数总数结果进行统计
divSum *= tmp;
}
// 短除法结束后,剩下的数如果不是 1,还需要对其进行统计
if(n != 1) divSum *= 2;
return divSum;
}
int main( )
{
// 获取输入
int n; cin>>n;
// 输出约数个数
cout<<getDivisorNum(n)<<endl;
return 0;
}
MT2204 约数之和
难度:黄金 时间限制:1秒 占用内存:128M
题目描述
给定正整数 n n n,求 n n n 的约数之和。
格式
输入格式:一个整数 n n n。
输出格式:输出一行一个整数表示答案。样例 1
输入:
12输出:
28备注
其中: 1 ≤ n ≤ 1 0 6 1\le n \le 10^6 1≤n≤106。
相关知识点:
数论
方法一:暴力求解
暴力求解数 n n n 的约数(因数)之和只需要通过循环遍历 1 ∼ n 1\sim\sqrt n 1∼n 中的全部数,并将所有的因数进行叠加即可。当然,在叠加时就需要统计 x x x 和 n x \frac{n}{x} xn 两个值( x x x 为因数)。同样地,对 int ( n ) \text{int}\left(\sqrt n\right) int(n) 需要进行特判,一旦 int ( n ) × int ( n ) \text{int}\left(\sqrt n\right)\times\text{int}\left(\sqrt n\right) int(n)×int(n) (即 n \sqrt n n 为整数时),就只需要算一个数。下面给出暴力求解的算法:
// 求指定数的约数之和
int getDivisorSum(int n)
{
int divSum = 1+n, limit = sqrt(n);
// 统计约数之和
for(int i=2; i<=limit; i++)
if(n%i == 0)
divSum += (i + n/i);
// 特判
if(limit*limit == n) divSum -= limit;
return divSum;
}
方法二:约数和定理
数 n n n 的约数(因数)之和实际上涉及到数论相关知识(约束和定理)。
前面我们曾提到,任意大于 1 的正整数 n n n 均可被分解为若干个质因数之积:
n = p 1 a 1 × p 2 a 2 × ⋯ × p k a k n=p_1^{a_1}\times p_2^{a_2}\times\dots\times p_k^{a_k} n=p1a1×p2a2×⋯×pkak
则其中 p i a i p_i^{a_i} piai 可生成的约数有 p i 0 , p i 1 , ⋯ , p i a i p_i^0,\ p_i^1,\ \cdots,\ p_i^{a_i} pi0, pi1, ⋯, piai 共 ( a i + 1 ) \left(a_i+1\right) (ai+1) 个。
而从约数的定义可知,约数是在 p 1 a 1 , p 2 a 2 , ⋯ , p k a k p_1^{a_1},p_2^{a_2},\cdots,p_k^{a_k} p1a1,p2a2,⋯,pkak 中分别挑选一个相乘得到,因此总的挑选方法有:
d ( n ) = ( 1 + a 1 ) + ( 1 + a 2 ) + ⋯ + ( 1 + a k ) d\left(n\right)=\left(1+a_1\right)+\left(1+a_2\right)+\dots+\left(1+a_k\right) d(n)=(1+a1)+(1+a2)+⋯+(1+ak)
由乘法原理可知,他们的总和就是这些情况分别相乘得到,即:
f ( n ) = ( p 1 0 + p 1 1 ⋯ + p 1 a 1 ) × ( p 2 0 + p 2 1 ⋯ + p 2 a 2 ) × ⋯ × ( p k 0 + p k 1 ⋯ + p k a k ) f\left(n\right)=\left(p_1^0+p_1^1\cdots+p_1^{a_1}\right)\times\left(p_2^0+p_2^1\cdots+p_2^{a_2}\right)\times\cdots\times\left(p_k^0+p_k^1\cdots+p_k^{a_k}\right) f(n)=(p10+p11⋯+p1a1)×(p20+p21⋯+p2a2)×⋯×(pk0+pk1⋯+pkak)
对每一项 ( p i 0 + p i 1 ⋯ + p i a 2 ) \left(p_i^0+p_i^1\cdots+p_i^{a_2}\right) (pi0+pi1⋯+pia2),都是一个首项为 1,等比为 p i p_i pi 的等比数列,于是可将上式化简为:
f ( n ) = p 1 a 1 − 1 p 1 − 1 × p 2 a 2 − 1 p 2 − 1 × ⋯ × p k a k − 1 p k − 1 f\left(n\right)=\frac{p_1^{a_1}-1}{p_1-1}\times\frac{p_2^{a_2}-1}{p_2-1}\times\cdots\times\frac{p_k^{a_k}-1}{p_k-1} f(n)=p1−1p1a1−1×p2−1p2a2−1×⋯×pk−1pkak−1
即:
f ( n ) = ∏ λ = 1 k p λ a λ − 1 p λ − 1 f\left(n\right)=\prod_{\lambda=1}^{k}\frac{p_\lambda^{a_\lambda}-1}{p_\lambda-1} f(n)=λ=1∏kpλ−1pλaλ−1
所以,采用约束和定理求解此题时,需要首先求出整个数列的质因数及其次数。由于前面已经给出了求解任意正整数的质因数方法,在此就不再赘述。下面直接给出求解本题的完整代码(已 AC):
/*
MT2204 约数之和
*/
#include
using namespace std;
// 求指定数的约数之和 (利用短除法求出所有质约数)
int getDivisorSum(int n)
{
int divSum = 1, tmp;
for(int i=2; i*i<=n; i++){
tmp = i;
// 统计质因数个数
while(n%i==0){
tmp *= i;
n /= i;
}
// 对约数和进行累乘统计
divSum *= (tmp-1)/(i-1);
}
// 短除结束后,剩下的数如果不是 1,还需要对其进行统计
if(n != 1) divSum *= (n+1);
return divSum;
}
int main( )
{
// 获取输入
int n; cin>>n;
// 输出约数个数
cout<<getDivisorSum(n)<<endl;
return 0;
}
MT2205 模数
难度:黄金 时间限制:1秒 占用内存:128M
题目描述
给定两个整数 a , b a, b a,b,问有多少个 x x x 使得等式 a m o d x = b a\ mod\ x\ =\ b a mod x = b 成立。如果存在无数个就输出
infinity
,否则输出满足条件的 x x x 的个数。格式
输入格式:两个整数 a , b a, b a,b。
输出格式:输出个数或infinity
。样例 1
输入:
21 5输出:
2备注
其中: 1 ≤ a , b ≤ 1 0 9 1\le a, b \le 10^9 1≤a,b≤109。
相关知识点:
数论
本题讨论 a m o d x = b a\ mod\ x\ =\ b a mod x = b 成立时, x x x 的取值情况。对于取模运算而言,该式需要讨论的情况实际有三类:
因此,本题实际上也是在考察约数,不过是具有特殊限制的约数。由于前面已经给出了求约数的具体算法,故下面直接给出求解本题的完整代码(已 AC):
/*
MT2205 模数
*/
#include
using namespace std;
// 求能满足两个指定数的模数个数
// 即:a mod x = b 的 x 解个数
int getModnum(int a, int b)
{
if(a < b) return 0;
if(a == b) return -1;
a -= b;
int modnum = 0, limit = sqrt(a);
for(int i=1; i<=limit; i++)
if(a%i == 0){
if(i > b) modnum++;
if(a/i > b) modnum++;
}
// 特判
if(limit*limit == a && a/limit > b) modnum--;
return modnum;
}
int main( )
{
// 获取输入
int a, b; cin>>a>>b;
// 输出约数个数
int ans = getModnum(a, b);
if(ans<0) cout<<"infinity"<<endl;
else cout<<ans<<endl;
return 0;
}
MT2206 tax
难度:钻石 时间限制:1秒 占用内存:128M
题目描述
小码哥要交税,交的税钱是收入 n n n 的最大因子(该最大因子为不等于 n n n 的最大因子),但是现在小码哥为了避税,把钱拆成几份(每份至少为 2),使交税最少,输出税钱。
格式
输入格式:一个正整数 n n n 表示收入(总钱数)。
输出格式:输出一个正整数表示税钱。样例 1
输入:
4输出:
2备注
对于30%的数据: 2 ≤ n ≤ 100 2\le\ n\le100 2≤ n≤100;
对于50%的数据: 2 ≤ n ≤ 10000 2\le\ n\le10000 2≤ n≤10000;
对于100%的数据: 2 ≤ n ≤ 2 × 10 9 2\le\ n\le{2\times10}^9 2≤ n≤2×109。
相关知识点:
数论
首先对题目进行解读。
现要求对一个数 n n n 进行拆分,你可以将其任意分为若干份(但每份的值不能低于 2)。接下来对每一份,取该数值的最大因数(不包括自己,下同)为税钱,然后统计总税钱,并使该税钱尽可能少。例如当 n = 8 n=8 n=8 时,有以下四种拆分方式:
从上面的拆分结果来看,显然将 8 分为 {3, 5} 能使最终交的税最少。在这情况下,8 被分为两个质数,因此他们的最大因数均为 1,于是最终的税费即为被划分的份数。
本题的任务就是输入一个数 n n n ,然后输出能够缴纳的最少税费。
从上面的例子可以看出,要想得到最少税费,那一定要想办法将数 n n n 划分为两个质数之和。于是乎,这就不得不提到世界近代三大数学难题之一——哥德巴赫猜想。
哥德巴赫于1742年在给欧拉的信中提出了以下猜想:任一大于2的整数都可写成三个质数之和。但是哥德巴赫自己无法证明它,于是就写信请教赫赫有名的大数学家欧拉帮忙证明,但是一直到死,欧拉也无法证明。1966年陈景润证明了“1+2”成立,即
任一充分大的偶数都可以表示成二个素数的和
。
于是,我们可以对原问题进行以下分类讨论:
基于此,可写出求解本题的完整代码(已 AC):
/*
MT2206 tax
哥德巴赫猜想:任一大于2的偶数都可以表示成二个质数之和
*/
#include
using namespace std;
// 判断一个数是否为质数
bool isPrime(int n)
{
int limit = sqrt(n);
for(int i=2;i<=limit;i++)
if(n%i==0)
return false;
return true;
}
// 根据收入确定税钱
int getTax(int n)
{
// 如果收入是质数则税钱为 1
if(isPrime(n)) return 1;
// 如果收入是偶数(大于2),则税钱为 2(哥德巴赫猜想)
if((n&1)==0) return 2;
// 如果收入是奇数,由于任何奇数都可以拆分为 奇数+偶数 之和
// 则只需要判断该数拆分为 奇数+2 时,奇数是否为质数,则最低税钱为 2
else if(isPrime(n-2)) return 2;
// 否则就将该数拆分为 质数+偶数,则最低税钱为 3(注:除 2 以外的所有质数一定是奇数)
else return 3;
}
int main( )
{
// 获取输入
int n; cin>>n;
// 输出税钱
cout<<getTax(n)<<endl;
return 0;
}
MT2207 数树
难度:黄金 时间限制:1秒 占用内存:128M
题目描述
在卡兹戴尔有一片很奇怪的森林,在一个直角坐标系内的 ( x , y ) \left(x,y\right) (x,y) 坐标值都为自然数的坐标上都有一颗树,如果一棵树的坐标 ( x , y ) \left(x,y\right) (x,y) 与原点 ( 0 , 0 ) \left(0,\ 0\right) (0, 0) 的连线中没有通过其他任何树,则称该树在原点处是可见的。
例如,树 ( 4 , 2 ) \left(4,\ 2\right) (4, 2) 就是不可见的,因为它与原点的连线会通过树 ( 2 , 1 ) \left(2,\ 1\right) (2, 1)。
部分可见点与原点的连线如下图所示,如图是一个 4 × 4 4\times4 4×4 的树林。请你计算出在一个 n × n n\times n n×n 的树林中可见的树有多少。
格式
输入格式:第一行为一个整数 c c c,表示共有 c c c 组测试数据,每组测试数据占一行,为整数 n n n。
输出格式:每组测试数据的输出占据一行。分别为测试数据的编号(从 1 开始),该组测试数据对应的 n n n 以及可见点的数量(同行数据之间用空格隔开)。样例 1
输入:
4
2
4
5
231输出:
1 2 5
2 4 13
3 5 21
4 231 32549备注
其中: 0 ≤ x , y ≤ n , 1 ≤ n ≤ 2000 , 1 ≤ c ≤ 10 0\le x,y\le\ n,\ 1\le n\le2000,\ 1\le c\le10 0≤x,y≤ n, 1≤n≤2000, 1≤c≤10。
相关知识点:
数论
从图中可以很直观地看出,只要两个点的斜率相等(如果存在),则其中坐标值更大的点必定是不可见的。换言之,对任意坐标 ( x , y ) \left(x,y\right) (x,y) ,如果 x , y x,y x,y 的最大公约数不为 1,就说明存在比这个点更小的(整数倍放缩)点 ( x 0 , y 0 ) \left(x_0,y_0\right) (x0,y0)(即有 y x = λ y 0 x 0 \frac{y}{x}=\lambda\frac{y_0}{x_0} xy=λx0y0 成立,其中 λ \lambda λ 为大于 1 的正整数),那么 ( x , y ) \left(x,y\right) (x,y) 就是不可见的。
所以,这道题依然考察了约数的相关内容。具体解法也很简单,遍历全部数据点 ( x , y ) \left(x,y\right) (x,y),所有 x x x 与 y y y 的最大公约数为 1 的点都是可见的,将其进行计数即可。此外,考虑到题目给出的是一个 n × n n\times n n×n 的方阵(具有对称性),因此我们只需要遍历其中的三角阵,在计数时统计 2 个即可,下面给出求解本题的完整代码:
/*
MT2207 数树
*/
#include
using namespace std;
// 求两个数的最大公约数
int gcd(int a, int b)
{ return b==0?a:gcd(b, a%b); }
// 对规格为 n×n 的树林进行遍历,查找可见树的数量
int getVisibleTrees(int n)
{
int vt = 2;
for(int i=1; i<=n; i++){
for(int j=1; j<i; j++)
if(gcd(i,j) == 1)
vt += 2;
}
return vt+1;
}
int main( )
{
// 获取输入
int c, n, vt; cin>>c;
for(int i=1; i<=c; i++){
cin>>n;
cout<<i<<" "<<n<<" "<<getVisibleTrees(n)<<endl;
}
return 0;
}