约数的个数。
给出正整数n的唯一分解式,求n的正约数的个数。
不难看出,n的任意正约数也只能包含p1,p2,p3等素因子,而不能有新的素因子出现。对于n的某个素因子pi,它所求约数中的指数可以是0,1,2,...ai共ai+1种情况,而且不同的素因子之间相互独立。根据乘法原理,n的正约数个数为:
小于n且与n互素的整数个数。给出正整数n的唯一分解式 ,求1,2,3,....n中与n互素的数的个数。
输入:正整数n。
输出:小于n且与n互素的整数个数。
运行结果:
用容斥原理。首先从总数n中分别减去是p1,p2,...pk的倍数的个数(对于素数p来说,“与p互素”和“不是p的倍数”等价),即,然后加上“同时是两个素因子的倍数”的个数,再减去"同时是3个素因子的倍数" .写成一个学术味比较浓的公式就是
这里引入的新记号就是题目汇总所求的结果,称为欧拉函数。强烈建议初学者花一些时间理解这个公式。对于{p1,p2,...pk}的任意子集,“不与其中任何一个互素”的元素个数是,不过这一项的前面的是加号还是减号呢?这取决于S中的元素个数-奇数个就是减号,偶数个就是加号。
公式已得出,可计算起来很不方便。如果直接根据公式,需要计算多大2^k项的代数和,甚至可能比“暴力枚举”(依次判断1-n中每个数是否与n互素)还要慢。
下一步并不显然。上述公式可以变形成如下的形式:
从而只需要O(k)的计算时间,在刚才的基础上大大提高了效率。为什么这个式子和上一个等价呢?直接考虑新公式的展开方式即可。展开式的每一项是从每个括号各选一个(选1或者-1/pi),全部乘起来以后再乘以n得到。这不正是最初的推导过程吗?
如果没有给出唯一分解式,需要用试除法依次判断内的素数是否是n的因子。这样,则需要先生成内的素数表,但其实并不用这么麻烦,只需要每次找到一个素因子之后把它除干净,即可保证找到的因子都是素数。
int euler_phi(int n)
{
int i, m, ans;
ans = n;
m = sqrt(n + 0.5);
for(i = 2; i <= m; i++)
{
if(n % i == 0)
{
ans = ans / i * (i-1);
while(n % i == 0)
n /= i;
}
}
if(n > 1)
ans = ans / n * (n-1);
return ans;
}
1~n中所有数的欧拉phi函数值。并不需要依次计算,可以用与筛法求素数非常类似的方法,在O(nloglogn)时间内计算完毕。
原理:寻找素数,每个素数的倍数的phi值一定是包含一项 值的,所以找出所有的倍数并且更新它的phi值。最后所有的素因子都找到了,也就得到了其所有倍数的phi值。
void phi_table(int n, int *phi)
{
int i, j;
for(i = 2; i <= n; i++)
phi[i] = 0;
phi[1] = 1;
for(i = 2; i <= n; i++)
if(!phi[i]) //保证pi是素数
for(j = i; j <= n; j += i) //只要有i的素数因子, 就进行计算
{
if(!phi[j])
phi[j] = j;
phi[j] = phi[j] / i * (i-1);
}
}