您的位置:首页 > 其它

数论知识点3——欧拉函数

2016-07-25 16:26 281 查看

数论知识点3——欧拉函数

1.欧拉函数

        指的是不超过n且与n互为素数的正整数的个数,并且如果n是一个素数,欧拉函数的值就等于n - 1,比如对于12来说,1, 5,7,11与它互质,GCD为1,所以φ(12) = 4;通式为φ(x) = x * (1 - 1 / p1) * (1 - 1/p2) * (1 - 1/p3)其中,p为x的素因子,例如对于12来说,素因子是2 3,所以φ(12) = 12
* (1/2) * (2/3) = 4,由此可以推出欧拉函数的普通求解法:

int phi(int x)
{
int ans = x;
for(int i = 2; i <= x; ++i)
{
if(x % i == 0)
{
ans = ans - ans / i;    //φ(x) = x * (1 - 1 / p1) * (1 - 1/p2) * (1 - 1/p3)
// 把第一个x放入第一个括号中得 x - x / p1,再把这个结果放入第二个括号
while(x % i == 0)
x /= i;
}
}
return ans;
}


       上面代码有一个致命问题就是超时,时间复杂度是O(n),现在考虑到任何一个合数都有不超过sqrt(n)的素因子,所以这个算法可以写成:

int phi(int x)
{
int ans = x;
for(int i = 2; i * i <= x; ++i)
{
if(x % i == 0)
{
ans = ans - ans / i;
while(x % i == 0)
x /= i;
}
}
if(x > 1)
ans = ans - ans / x;
return ans;
}

[align=left]         这个代码是不是感觉和试除法求因子特别像。看一道题题目吧[/align]

2.POJ 2407

        题目意思很简单,就是求给定数字的欧拉函数值,由于数据在1~1000000000之间,所以要用第二份代码,才不会超时。

3.欧拉函数打表

        就像素数一样,如果我们需要经常判断使用欧拉函数的值,不如提前打一张表,打表的过程和素数筛法非常类似,代码如下:

const int maxn = 4000005;
LL phi[maxn];

void init()
{
memset(phi, 0, sizeof(phi));
phi[1] = 1;
for(int i = 2; i < maxn; ++i)
{
if(!phi[i])
{
for(int j = i; j < maxn; j += i)
{
if(!phi[j])
phi[j] = j;
phi[j] = phi[j] / i * (i - 1);
//phi(n) = n(1 - 1/p1)(1 - 1/p2)....
}
}
}
}
         欧拉函数的打表过程并是一次phi[j] = phi[j] / i  * (i - 1)就可以完成的,我们知道欧拉函数的通式表明一个欧拉函数是由它的素因子构成的,所以打表过程中,首先将所有含有素因子数2的整数,先乘上x * (1 - 1/ 2),然后将所有含有素因子数3的整数,以刚刚的结果或者是原数本身再乘上(1 - 1 / 3),依次循环,所以才说这个和埃式筛法特别的相似。

4.POJ 2478

      这道题就是求法雷序列的个数,看题干给的样例,对于每一个序列,如果我们把分母相同的归为一类,就可以看出实际上f(x) = f(x - 1) + φ(x),也就是这道题就是求解欧拉函数的累加和,所以先打表然后累加,然后输出结果即可

const int maxn = 1e6 + 5;
LL phi[maxn];
LL ans[maxn];

void init()
{
memset(phi, 0, sizeof(phi));
phi[1] = 1;
for(int i = 2; i < maxn; ++i)
{
if(!phi[i])
{
for(int j = i; j < maxn; j += i)
{
if(!phi[j])
phi[j] = j;
phi[j] = phi[j] / i * (i - 1);
//phi(n) = n(1 - 1/p1)(1 - 1/p2)....
}
}
}
ans[1] = 0;
for(int i = 2; i < maxn; ++i)
ans[i] = ans[i - 1] + phi[i];
}

int main()
{
#ifdef LOCAL
///freopen("in.txt", "r", stdin);
///freopen("out.txt", "w", stdout);
#endif // LOCAL
init();
int num;
while(cin >> num && num)
{
cout << ans[num] << endl;
}
return 0;
}


5.POJ 1284

         这道题虽然代码简单,但是用到了一个知识点,就是原根的概念,设m是正整数,a是整数,若a模m的阶等于φ(m),则称a为模m的一个原根。这道题就是求原根的个数的题目。即为φ(φ(m)),由于m是一个素数,所以φ(m)
= m - 1,即最终结果为φ(m - 1).

int phi(int x)
{
int ans = x;
for(int i = 2; i * i <= x; ++i)
{
if(x % i == 0)
{
ans = ans - ans / i;
while(x % i == 0)
x /= i;
}
}
if(x > 1)
ans = ans - ans / x;
return ans;
}

int main()
{
#ifdef LOCAL
///freopen("in.txt", "r", stdin);
///freopen("out.txt", "w", stdout);
#endif // LOCAL
int n;
while(cin >> n)
{
cout << phi(n - 1) << endl;
}
return 0;
}


6.POJ 3090

        题意的意思是说现在有一个坐标系,给你一个范围,在这个范围内,从原点出发画一条线段到(x, y)点,假设这条线段不经过其他的点,那么就是成立的,问有多少种情况,表面上是一个几何题目,实际上就是一道简单的数论题目
        考虑。如果一条线段不能再经过其他的点了,
        那么,首先他的终点x,y坐标必须是互质的,不然肯定就可以除以他们的公因子得到一个新的点,假设现在y固定,去找有多少个x和y互质。这不就是欧拉函数吗?然后我们把x固定,同理可得。
        其次如果x,y其中一个为0,那么这种情况只有两种也就是(0,1),(1,0)除此之外任何为0的点肯定经过(0,1),(1,0)
        最后,如果x == y那么也只有一种情况,也就是(1,1)理由同上。
        所以思路就出来了,先打一张欧拉函数表,然后求欧拉函数的累加和的2倍,记得加上3.

static final int maxn = 1005;
static final int maxn = 1005;
static int[] phi = new int[maxn];
static int[] ans = new int[maxn];

static void init() {
    phi[1] = 1;
    for(int i = 2; i < maxn; ++i) {
        if(phi[i] == 0) {
            for(int j = i; j < maxn; j += i) {
                if(phi[j] == 0) {
                    phi[j] = j;
                }
                phi[j] = phi[j] / i * (i - 1);
                    
            }
        }
    }
    
    ans[0] = 0;
    ans[1] = 3;
    for(int i = 2; i < maxn; ++i) {
        ans[i] = ans[i - 1] +  phi[i] * 2;
    }
}

public static void main(String[] args) {
    init();
    Scanner cin = new Scanner(System.in);
    int t;
    int n;
    t = cin.nextInt();
    for(int i = 1; i <= t; ++i) {
        n = cin.nextInt();
        System.out.println(i + " " + n + " " + ans
);
    }
    cin.close();
}
        其实这些满足条件的点,和法雷序列特别像,我不知道有没有关系,现在法雷序列只用过一次。。。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: