求二进制小数循环节
这题必须要先吐槽一下出题的人,某些细节没有写清楚,比如整除的情况下是怎样?(或许没有那样的数据....还有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;
}