理科男【数论】
题目大意:
对求分数,求其在进制下是有限小数还是循环小数。如果是有限小数,求小数点后的位数;如果是循环小数,则求混循环部分和循环节的长度又分别是多少。
3
1 8 10
17 99 10
217 990 10
3 0
0 2
1 2
思路:
这道数论,好难啊。。。
证明我肯定是看不懂了。只能按照题解所说的打,但是什么都不懂。
在这里把 题解 的证明放一下。
首先把约分成既约分数。设 , 为原分数小数点后第位的数。
显然有 。
剩下来的余数 。
依此类推我们有 , 。
不难发现如果 ,那么小数点后第 位到第 位这一段就可以视为一
个循环节。
暴力计算数列 ,找到第一个与前面重复的项,就可以找到最短循环节了。
这个重复的项前面的部分导出混循环部分。
如果最早在 处计算到 ,那么原分数就是一个小数点后有 位的有限小数。
以上便是 50 分的解法。
50分解法还很容易理解,但是接下来的100分做法就真的很难理解了。。。(也许是我太菜了吧)
下面我们对 数列的性质做一些讨论。
如果 ,对于任意的 都有 。
设 为 时的乘法逆元,即 。由乘法逆元的性质 存在且唯一。
假设最早出现重复的位置是 。
如果 ≠ ,那么 。
也就是出现了更早的重复,与题设矛盾。所以显然有 。
这时,显然原分数是一个纯循环小数,且最短循环节长度是 。
设 。显然 ,于是 。
这就转化成了求 的阶的问题了。
由欧拉定理 ,由阶的性质 。
我们可以将 分解素因数,并初始化 。
之后考虑 的每个素因数 。如果 ,就 ,并继续试除 。
否则转下一个素因数。这样就可以求出 的阶了,这就是最短循环节的长度。
如果 ,那么 。设 ,不难发现对于任意的 ,有 。
不妨设 ,。
若此时 ,就转化为了上面的情况。否则继续这个过程。
如果上面的转换进行了 次,由于 到 与后面 数列的循环无关。
卡一下范围便会知道循环节的最后一个数字与混循环部分最后一个数字一定不相等。
于是原分数的混循环长度就是 了。
特殊地,如果在 次转换之后得到的最后一个 ,那么之后 数列的值全为 0。这时
我们可以断言原分数是一个小数点后有 位的有限小数。
以上便是 100 分的做法。
好吧这么多我知道没人会看
代码:
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
long long n,a,k,sum,gcd,b,num,ans;
long long phi(long long x)
{
long long ans=x;
for (long long i=2;i*i<=x;i++)
if (!(x%i))
{
ans=ans/i*(i-1);
while (!(x%i)) x/=i;
}
if (x>1) ans=ans/x*(x-1);
return ans;
}
long long binary(long long x,long long y,long long o)
{
long long ans=0;
for (;y;y>>=1)
{
if (y&1) ans=(ans+x)%o;
x=(x<<1)%o;
}
return ans;
}
long long ksm(long long x,long long y,long long o)
{
long long ans=1;
for (;y;y>>=1)
{
if (y&1) ans=binary(ans,x,o);
x=binary(x,x,o);
}
return ans;
}
int main()
{
scanf("%lld",&n);
while (n--)
{
scanf("%lld%lld%lld",&a,&b,&k);
gcd=__gcd(a,b);
a/=gcd;
b/=gcd;
sum=0;
while (__gcd(b,k)>1)
{
sum++;
gcd=__gcd(b,k);
b/=gcd;
}
printf("%lld ",sum);
if (b==1)
{
printf("0\n");
continue;
}
num=ans=phi(b);
for (int i=2;i*i<=num;i++)
if (!(num%i))
{
while ((!(ans%i))&&ksm(k,ans/i,b)==1) ans/=i;
while (!(num%i)) num/=i;
}
if (num>1&&ksm(k,ans/num,b)==1) ans/=num;
printf("%lld\n",ans);
}
return 0;
}