POJ 3358 数论

求二进制小数循环节

这题必须要先吐槽一下出题的人,某些细节没有写清楚,比如整除的情况下是怎样?(或许没有那样的数据....还有p=0的情况是怎样,这些特殊情况很难说清),数据范围也没用说明。结果把本菜坑的一塌糊涂。。。(T..T)

回归正题:求二进制小数的循环节和循环起点。刚开始这题后来没思路,看解题报告发现人家把小数化为分数时,果断关闭窗口。自己去独立完成。。。(话说这也没想到也忒2B了吧。。。。T...T)这样就找到了规律:

以1/10为例:1/10 2/10 4/10 8/10 16/10 32/10 64/10....(小数话二进制每次*2取整,这不就和分子*2,以分母为余数取模一样吗。。。NC无下限)

取模后:1/10 2/10 4/10 8/10 6/10 2/10 4/10 咦,这不就是个循环吗?循环节为4,循环起点为2,正好与题目相符。。如何去找循环节和循环起点?

由于是二进制,所以分子可以表示为2^x,而模数即q

2^x=2^y(mod p),2^x(2^(y-x)-1)=0(mod p),即p|2^x(2^(y-x)-1),首先把p尽量整除2直到不能整除为止,这个步骤的次数就是满足原式最小的x,并得到p'。2^(y-x)=1(mod p')

根据欧拉定理,t=y-x=phi(p')满足此式。但不是最小值,枚举phi(p')约数。

话说这题被坑了N多个WA。。。题目没说清,自己有两处处理的不好,哎。。。伤不起呀。。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <vector>

#define ss(p,q) scanf("%I64d/%I64d",&p,&q)
#define N 1000005
#define M 30
#define ll __int64
#define pb push_back
#define Max 9223372036854775807ULL

using namespace std;

ll a[N];
vector<int>b;

void prime()
{
    int i,j;
    b.clear();
    for (i=2;i<N;i++)
        if (!a[i])
        {
            b.pb(i);
            for (j=i*2;j<N;j+=i) a[j]=1;
        }
}

ll euler(ll n)
{
    ll res=n,i;
    if (n==1) return 0;
    for (i=0;i<b.size();i++)
        if (n%b[i]==0) 
        {
            res=res/b[i]*(b[i]-1);
            while (n%b[i]==0) n/=b[i];
            if (n==1) break;
        }
    if (n!=1) res=res/n*(n-1);
    return res;
}

ll gcd(ll p,ll q)
{
    if (p<q) return gcd(q,p);
    else if (p%q==0) return q;
    else return gcd(q,p%q);
}

ll js(int x,ll q)
{
    ll i=0,res=1,curr=2%q;
    while (x>0)
    {
        if (x&1) res=(res*curr)%q;
        i++;
        x>>=1;
        curr=(curr*curr)%q;
    }
    return res;
}

ll deal(ll t,ll q)
{
    int i,i0;
    ll mmin=Max;
    for (i=1;i*i<=t;i++)   
        if (t%i==0)
        {
            if (js(i,q)==1) return i;
            i0=t/i;
            if (js(i0,q)==1&&i0<mmin) mmin=i0;
        }
    return mmin;
}

int main()
{
    ll p,q,i,t,x,cas=0;
    prime();
    while (ss(p,q)!=EOF)
    {
        cas++;
        x=0;
        if (p==0)
        {
            cout<<"Case #"<<cas<<": "<<1<<','<<1<<endl;
            continue;
        }
        ll y=gcd(p,q);
        p/=y;q/=y;
        ll q1=q;
        while (q1%2==0)
        {
            x++;
            q1/=2;
        } 
        x++;
        t=euler(q1);
        if (t!=0) t=deal(t,q1); 
        cout<<"Case #"<<cas<<": "<<x<<','<<t<<endl;
    }
    return 0;
}


 

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值