http://poj.org/problem?id=3358
题意:
给你一个有理分数,要你求这个分数写成2进制小数时的最小循环节和最开始的位置
思路:
一开始以为可以直接暴力模拟搞,因为没有给p、q的范围,写了一个暴力的代码,一
交RE了,更了几次还是RE,说明算法不对。 后来就直接用数论的方法去做了,具体
的思路是这样的:假设分数为p/q,先p/=gcd(p,q) ,q/=gcd(p,q),因为是找循环节,所
以只要找一个i和j使得: p*2^i = p*2^j ( mod q ),(我们这里假设i < j),因此上式又可
以转化为:p*2^i-p*2^j = 0( mod q ), p*2^i*(1-2^(j-i)) = 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是一定不会有2的因子的 ,所有q中2的因子应该都是由2^i来消除,假设
剩余的q为q1, 因此我们又可以得到下面的式子:q1 | ( 2 ^(j-i) - 1) ,令j-i = x , 这样我们
就可以转化2^x = 1(mod q1), 这是一点高阶同余方程,有经典的求法。我们可以利用
欧拉定理来做,欧拉定理告诉我们:a^phi(p) = 1( mod p )当且仅当 gcd(a,n) = 1时成立。
这样,在我们的式子中,gcd(2 , q1) = 1 ,所以我们只需要求出q1的欧拉函数值就可以
了,但是还要要求我们所求的x最小,这样我们就可以枚举r | phi(q1) ,求出最小的x。
代码:
#include<stdio.h> #include<string.h> typedef long long LL ; LL p ,q ; char ch[1000000] ; LL ans1 , ans2 ; LL gcd(LL a , LL b){ while(b){ LL c = a ; a = b ; b = c % b ; } return a ; } LL cal_phi(LL n){ LL res = n ; for(LL i=2;i*i<=n;i++){ if( n%i == 0 ){ n /= i; for( ;n%i==0;n/=i) ; res = res/i*(i-1) ; } } if(n > 1) res = res/n*(n-1) ; return res ; } bool is_ok(LL a ,LL b , LL c){ LL res = 1 , add = a ; while(b){ if(b & 1){ res = res * add % c ; } add = add * add % c ; b /= 2 ; } return res == 1; } LL min(LL a , LL b){ return a > b ? b : a ; } void solve(LL q){ LL phiq = cal_phi(q) ; ans2 = phiq ; for(LL r=1;r*r<=phiq;r++){ if(phiq % r != 0) continue ; if( is_ok(2,r,q) ){ ans2 = min(ans2, r) ; } if( is_ok(2,phiq/r,q) ){ ans2 = min( ans2 , phiq / r ); } } } void calc(){ int i , j ; j = 0 ; LL res = q ; while(res % 2 == 0 ){ j++ ; res /= 2 ; } ans1 = j + 1 ; if(res == 1){ ans2 = 1 ; } else solve( res ); printf("%lld,%lld\n",ans1,ans2); } int main(){ int i ,cas=0 ; while(scanf("%s",ch) == 1){ ++cas ; int len = strlen(ch); p = q = 0 ; for(i=0;i<len;i++){ if(ch[i] == '/') break ; p = p * 10 + ch[i] - '0' ; } for(i++;i<len;i++){ q = q * 10 + ch[i] - '0' ; } LL g = gcd(p,q) ; p /= g ; q /= g ; printf("Case #%d: ",cas); calc() ; } return 0; }