欧拉函数的应用(2)——欧拉函数与欧拉定理的结合

例题一:The Luckiest number(pku 3696)

中国人喜欢的数字是6,8,9.Bob也一样,他喜欢的数字是8.Bob的幸运数字为能整除L的全8序列的最短长度。现在的任务就是找到Bob的幸运数字,如果不能找到,则输出0.

 如果L=1,则幸运数字应该是一,因为8只能整除一;同理,当L=2时,幸运数字同样是一。

输入:输入数据有多组,每组占一行,每组测试数据输入一个L(1<=L<=2 000 0000 000).当L=0时,输入结束。

输出:对应每组输入数据输出占一行,输出格式为:测试数据的编号(从1开始),Bob的幸运数字。具体见样例。

Sample Input
8
11
16
0
Sample Output
Case 1: 1
Case 2: 2
Case 3: 0

分析:题意,给一个数N(1<=N<=2000000000);问是否存在N的倍数M,且M的各个位全部由8组成,如果存在多个取最小的M并输出M由几个8组成。

解题思路:因为M全部由8组成,即M=(10^x -1)*8/9=k*N;

则    (10^x-1)*8/gcd(8,N)=9*k*N/gcd(8,N);

令p=8/gcd(8,N);     q=9*N/gcd(8,N);    即    (10^x-1)*p=k*q;

 由于p和q互质(因为8/gcd(8,N)与N/gcd(8,N)互质) ,则(10^x-1)%q==0;

根据同余定理可知,10^x ≡1(mod q)

根据欧拉定理可知当gcd(a,b)==1时,a^φ(b)≡1(mod b);

即可得出:当gcd(10,q)==1时    10^φ(q)≡1(mod q)   即通过枚举φ(q)的因子(最小因子)就能得出结果.x=φ(q)一定是一个解,但不一定是最小的解,若x=φ(q)不是最小的解,那么最小的解x一定是φ(q)的因子.

证明:假设x是满足2^x≡1(mod n)的最小的x,假设x不是φ(n)的因子,令r=φ(n)mod x,则r>0&&r<x.由2^φ(n)≡ 1(mod n)和2^x≡1(mod n),可以得2^r mod n=1,则存在一个比x还小的数,使得2^r≡1(mod n),这与假设矛盾,所以x为φ(n)因子。

最后检查模是否为1即可。具体求解步骤如下:

(1)求解x=φ(q)

(2)找出φ(q)的所有素因子Pi;

(3)让x=x/pi,直到10^x! ≡1(mod q),或pi不能整除x,如果10^x!≡ 1(mod m),x=x*pi;

(4)重复(3),直到所有的素因子pi都经过(3)处理;

(5)x就是满足10^x≡1(mod m)最小的x

那么何时没有解呢?那就是gcd(10,m)!=1时,证明:

1.若d=2,q=2*k,那么10^x=2^x*5^x=1%2k
   即2^x*5^x=1+2k*m,左边为偶数,右边为奇数,显然矛盾。
2.若d=5,q=5*k,那么10^x=2^x*5^x=1%5k
   即2^x*5^x=1+5k*m,左边是5的倍数,右边不是5的倍数,显然矛盾。

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
long long gcd(long long a,long long b)///最大公因数
{
    if(b==0)
        return a;
    return gcd(b,a%b);
}
long long mo;
long long multi(long long a,long long b)///(a*b)%mo
{
    long long n=0;
    while(b)
    {
        if(b&1)
        {
            n=(n+a)%mo;
            b--;
        }
        b>>=1;
        a=(a<<1)%mo;
    }
    return n;
}
long long quick_mod(long long x)///x为幂的值
{
    long long ans=1;
    long long m=10;
    while(x)
    {
        if(x&1)
        {
            ans=multi(ans,m);
            x--;
        }
        x>>=1;
        m=multi(m,m);
    }
    return ans;
}
int main()
{
    int t=0;
    while(scanf("%lld",&mo)!=EOF)
    {
        if(mo==0)
            break;
        printf("Case %d: ",++t);
        mo=mo*9/gcd(mo,8);
        if(gcd(mo,10)!=1)///解不存在的条件
        {
            printf("0\n");
            continue;
        }
        long long rear=mo,n=mo;
        long long prime[50][2];
        int k=0;///k记录了素因子的个数
        for(long long i=2;i*i<=n;i++)///求的是mo的欧拉函数
            if(n%i==0)
        {
            rear=rear-rear/i;
            while(n%i==0)
                n/=i;
        }
        if(n>1)
            rear=rear-rear/n;

        n=rear;
        for(long long i=2;i*i<=n;i++)///求的是欧拉函数phi[mo]的素因子及其个数
            if(n%i==0)          ///用二维数组的对应关系存储素因子及其个数
        {
            prime[k][0]=i;
            prime[k][1]=0;
            while(n%i==0)
            {
                n/=i;
                prime[k][1]++;
            }
            k++;
        }
        if(n>1)
        {
            prime[k][0]=n;
            prime[k][1]=1;
         }

        for(int i=0;i<=k;i++)///素因子的个数
            for(int j=1;j<=prime[i][1];j++)///每个素因子的个数
            if(quick_mod(rear/prime[i][0])==1)
            rear/=prime[i][0];
        printf("%lld\n",rear);
       }
       return 0;
}

例题二:Period of an Infinite Binary Expansion(pku 3358)

题目描述:令{x} = 0.a1a2a3.... 是一个数小数部分的二进制表示形式。假设{x}具有循环性,那么{x}就可以写成下面的形式

{x} = 0.a1a2...ar(ar+1ar+2...ar+s)w    

 对某个r和s成立(r>=0且s>0).(ar+1ar+2...ar+s)代表{x}二进制的无限循环部分。

序列x1=a1a2...ar 称为{x}的循环前缀,x2=ar+1ar+2...ar+s称为{x}的循环部分。

让|x1|和|x2|尽量小,那么x1称为最小前缀,x2称为最小循环。

例如:x=1/10=0.0001100110011(00110011)w,0001100110011是1/10的一个循环前缀,00110011是1/10的一个循环部分。

然而,1/10=0.0(0011)w ,0是1/10的最小前缀,1/10的最小循环部分从第二位开始,最小循环部分的长度为4.

把一个小于1的小数转化成二进制形式,编程找到最小循环部分的起始位置以及最小循环的长度。

输入: 每组测试数据占一行,输入整数p,q,代表了一个小数 p/q,p>=0且q>0.

输出:对应每组测试数据,输出占一行。输出两个整数,第一个整数代表最小循环的起始位置,第二个整数代表最小循环部分。具体输出格式详见样例;

Sample Input
1/10 
1/5 
101/120 
121/1472
Sample Output
Case #1: 2,4 
Case #2: 1,4 
Case #3: 4,4 
Case #4: 7,11
分析:

观察1/10这组数据,按照二进制转化法(乘二法),可以得到

1/10     2/10  4/10  8/10  16/10   32/10.。。。。

然后每个分子都尽可能减去10,得到

1/10  2/10  4/10  8/10  6/10  2/10.。。。

这时出现了重复,那么这个重复,就是要求的最小循环。

抽象出模型如下,对于p/q,首先将其化简成最简形式,p' =p/gcd(p,q),q'=q/gcd(p,q). p' * 2^i≡p' * 2^j(mod q').经过变换得到, p' * 2^i * ( 2^(j-i) -1 )≡0(mod q'),也就是q'p' * 2^i * ( 2^(j-i) -1 )由于gcd(p',q')=1,故有q' | 2^i * ( 2^(j-i) -1 ).

      因为2^(j-i)-1为奇数,所以q'有多少个2的幂,i的值就是多少了,而且i就是循环开始位置的前一位。令q''为q'除去2^i之后的数,此时有q'' | ( 2^(j-i) -1 ),也就是求出x,使得2^x≡1(mod q'').

   由于q''和2互素,所以(由欧拉定理:2^Φ(q'')≡1(mod q'') )此方程必有解,且Φ(q'')为方程的一个解。但不一定是最小解。这里和POJ3696有点相似,可知最小解为Φ(q'')的一个因子。

    解题步骤如下:

   (1)求解x=Φ(q'');

   (2)找出Φ(q'')的所有素因子pi;

   (3)令x=x/pi,直到2^x≠1(mod q'')或pi不能整除x。如果2^x≠1(mod q''),令x=x×pi;

   (4)重复步骤(3),直到所有的素因子都经过(3)处理;

   (5)此时x就是满足2^x≡1(mod q'')的最小解。

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int p,q;
int gcd(int a,int b)
{
    if(b==0)
        return a;
    return gcd(b,a%b);
}
int phi(int n)///求n的欧拉函数的值
{
    int rea=n;
    for(int i=2;i*i<=n;i++)
        if(n%i==0)
    {
        rea=rea-rea/i;
        while(n%i==0)
            n/=i;
    }
    if(n>1)
        rea=rea-rea/n;
    return rea;
}
int prime[50][2];///第一列存储素因子,第二列存储素因子的个数
int ct;///记录素因子的个数
void make_prime(int n)
{
    ct=0;
    for(int i=2;i*i<=n;i++)///基本的找出素因子的方法
        if(n%i==0)
    {
        prime[ct][0]=i;
        prime[ct][1]=0;
        while(n%i==0)
        {
            prime[ct][1]++;
            n/=i;
        }
        ct++;
    }
    if(n>1)
    {
        prime[ct][0]=n;
        prime[ct++][1]=1;
    }
}
long long quick_mod(long long b,long long m)///快速幂取模
{
    long long ans=1;
    long long a=2;
    while(b)
    {
        if(b&1)
        {
          ans=ans*a%m;
          b--;
        }
        b>>=1;
        a=a*a%m;
     }
     return ans;
}
int main()
{
   int t=1;
   while(scanf("%d/%d",&p,&q)!=EOF)
   {
       printf("Case #%d: ",t++);
       if(p==0)///特殊情况处理
       {
           printf("1,1\n");
           continue;
       }
       int g=gcd(p,q);
       p/=g;
       q/=g;
       int x=0;
       while((q&1)==0)
       {
           q>>=1;
           x++;
       }
       x++;
       
       int rear=phi(q);
       make_prime(rear);
       
       for(int i=0;i<ct;i++)///x可能为某个因子,所有的因子都是由素因子组合而来
        for(int j=1;j<=prime[i][1];j++)
        if(quick_mod((long long)rear/prime[i][0],(long long)q)==1)
        rear/=prime[i][0];
       printf("%d,%d\n",x,rear);
   }
   return 0;
 }





  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值