题目链接:http://poj.org/problem?id=3358
题目大意:给出一个小于1的分数,把他转换成二进制形式,并找出小数点到循环部分的最少距离以及循环节的最小长度。
分析:对于一个分数n/m,首先我们要把它化成最简形式:分别令n/=gcd(n,m),m/=gcd(n,m)。至于将一个十进制小数化为k进制小数的方法呢,我们有如下代码段(数组bit用来存放对应k进制的小数部分。):
while(true) { n=n*k; bit[i++]=n/m; n=n%m; }
抽象出模型如下:对于最简分数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 m)或pi不能整除x。如果2^x≠1(mod m),令x=x×pi;
(4)重复步骤(3),直到所有的素因子都经过(3)处理;
(5)此时x就是满足2^x≡1(mod m)的最小解。
实现代码如下:
#include <cstdio> #include <iostream> using namespace std; typedef long long ll; int p,q; int gcd(int a,int b) { return b?gcd(b,a%b):a; } int phi(int n) { int rea=n; for(int i=2;i*i<=n;i++) if(n%i==0) { rea=rea-rea/i; do n/=i; while(n%i==0); } if(n>1) rea=rea-rea/n; return rea; } int fac[50][2],ct; void get_fac(int n) { ct=0; for(int i=2;i*i<=n;i++) if(n%i==0) { fac[ct][0]=i; fac[ct][1]=0; do { fac[ct][1]++; n/=i; }while(n%i==0); ct++; } if(n>1) { fac[ct][0]=n; fac[ct++][1]=1; } } ll quick_mod(ll x,ll m) { ll ans=1,a=2; while(x) { if(x&1) ans=ans*a%m; a=a*a%m; x>>=1; } return ans; } int main() { int T=1; while(scanf("%d/%d",&p,&q)!=-1) { printf("Case #%d: ",T++); if(!p) { puts("1,1"); continue; } int gd=gcd(p,q); p/=gd,q/=gd; int ans1=1; while(!(q&1)) { q>>=1; ans1++; } int ans2=phi(q); get_fac(ans2); for(int i=0;i<ct;i++) for(int j=1;j<=fac[i][1];j++) if(quick_mod((ll)ans2/fac[i][0],(ll)q)==1) ans2/=fac[i][0]; printf("%d,%d\n",ans1,ans2); } return 0; }