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;
}