POJ_3358 Period of an Infinite Binary Expansion

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值