【数论,模拟】SSL 1474 循环小数

前言

东莞市小学生镇赛一道题。。。

描述

描述

题目大意

给定A和B,求出A/B的循环节及混循环部分

注:混循环部分,即除循环节外的部分,例如
34/35=0.9(714285)

9为混循环部分,714285为循环部分,即循环节

若A/B为正整数,则输出A/B.0,例如2/1=2.0

思路

两种方法都是建立在模拟高精除运算的基础上

方法一,通过商来求循环节

先利用数学公式求出混循环部分的长度,然后算出所有的商,再枚举循环节的长度,判断即可AC

时间复杂度: O ( l e n )

方法二,通过余数来求循环节

方法二较方法一更为优秀,我们只需判断当前余数是否出现,然后判断一下,输出即可,详见代码

时间复杂度: O ( + )

代码

方法一

#include<cstdio>
#include<iostream>
#include<iomanip>
#include<algorithm>
#include<cmath>
using namespace std;int n,m,d,k,len=0,a[501],j,b[1001],bj,len2;
inline int gcd(register int x,register int y){return y?gcd(y,x%y):x;}//辗转相除法 
signed main()
{
    scanf("%d%d",&n,&m);
    printf("%d/%d=",n,m);
    if(n%m==0) {printf("%d.0",n/m);return 0;}
    d=gcd(n,m);
    n/=d;m/=d;//约分 
    int B=m;
    while(1)//以下为计算混循环长度的方法,即混循环部分的长度为除数中2和5的倍数之和 
    {
        d=gcd(B,10);
        if(d==1) break;B/=d;
        len++;//计算其混循环部分的长度 
    }
    k=m;
    while(k!=1)//这是另一种求法,方法跟上面是一样的,这里打出来是为了方便理解 
    {
        if(!(k%5)) k/=5;//能被5整除就除以5 
        else if(!(k&1)) k>>=1;//能被2整除就除以2 
        else break;//两个都除不了 
    }
    if(k==1) cout<<fixed<<setprecision(len)<<n/1.0/m;//计算出小数位数长度,输出 
    else
    {
        printf("%d.",n/m);//首先计算出整数部分
        int g=n%m;//计算出整数部分的余数 
        int beg=(int)log(n)+1;//beg为除法开始运算的位置 
        while(n) a[++j]=n%10,n/=10;//转换成数组 
        reverse(a+1,a+1+j);//翻转 
        for(int i=beg+1,t=0;i<=500;i++)//模拟高精度除法 
        {
            g=(g<<3)+(g<<1)+a[i];//余数*10+a[i] 
            b[i]=g/m;//计算b 
            g%=m;//计算余数 
            if(++t<=len) putchar(b[i]+48);//若其为混循环部分,输出 
        }
        putchar('(');//以下为计算循环节 
        int i;
        beg+=len;//beg要加上len因为我们已经计算过了混循环部分 
        for(i=1;i<=250;i++)
        {
            bool ok=true;
            for(register int j=beg+1;j<=500-i;j++)  if(b[j]!=b[j+i]) 
            {ok=false;break;}
            if(ok) break;
        }
        for(register int j=beg+1;j<=beg+i;j++) putchar(b[j]+48);//输出循环节部分 
        putchar(')');//记得输出括号 
    }
}

方法二

#include<cstdio>
#include<iostream>
#include<iomanip>
#include<algorithm>
#include<cmath>
using namespace std;int n,m,d,k,len=0,a[501],j,b[1001],bj;
inline int gcd(register int x,register int y){return y?gcd(y,x%y):x;}
signed main()
{
    scanf("%d%d",&n,&m);
    printf("%d/%d=",n,m);
    if(n%m==0) {printf("%d.0",n/m);return 0;}
    d=gcd(n,m);
    n/=d;m/=d;//约分 
    int B=m;
    while(1)//以下为计算混循环长度的方法,即混循环部分的长度为除数中2和5的倍数之和
    {
        d=gcd(B,10);
        if(d==1) break;B/=d;
        len++;//计算其混循环部分的长度 
    }
    k=m;
    while(k!=1)//这是另一种求法,方法跟上面是一样的,这里打出来是为了方便理解 
    {
        if(!(k%5)) k/=5;//能被5整除就除以5 
        else if(!(k&1)) k>>=1;//能被2整除就除以2 
        else break;//两个都除不了 
    }
    if(k==1) cout<<fixed<<setprecision(len)<<n/1.0/m;//计算出小数位数长度,输出 
    else
    {
        printf("%d.",n/m);//首先计算出整数部分
        int g=n%m;//计算出整数部分的余数 
        int beg=(int)log(n)+1;//beg为除法开始运算的位置 
        while(n) a[++j]=n%10,n/=10;//转换成数组 
        reverse(a+1,a+1+j);//翻转 
        len++;//因为这是用余数去算的,位数要偏后一位,因为我们要晚一步计算 
        for(int i=beg+1,t=0;i<=500;i++)
        {
            if(++t==len) putchar('(');//混循环结束,循环节开始 
            g=(g<<3)+(g<<1)+a[i];//余数*10+a[i] 
            b[i]=g/m;
            g%=m;
            if(bj==g&&t>len){putchar(')');break;}//该余数已经出现过了,结束 
            putchar(b[i]+48);//输出 
            if(t==len) bj=g;//标记余数 
        }
    }
}

猜你喜欢

转载自blog.csdn.net/xuxiayang/article/details/81772272