vijos1047题解


总算编好了这一题,我表示200+行,亚历山大。
题目描述很简单,做起来不简单啊。(高精度的取模和除法不是一般的恶心!)
先说一下非高精度的一般做法。
求两个数a,b的最小公倍数,就是a、b的乘积与a、b的最大公因数的商。即:lcm(a,b)=a*b/gcd(a,b);
然而,如果a,b都是高精度数,我们不仅要算乘法和除法(看这个式子就知道了),还要有减法(因为mod运算中,c mod d=c-cdiv d*d)
求gcd(a,b)(最大公因数)一般有两种方法:辗转相除法(见http://baike.baidu.com/link?url=7yVbCI_TCmVcqMbEu_BgYb0ejvSJDZVmkw9g0UjrMU_3yaHAAFABgfjuk1ZT5rgI和更相减损法(见http://baike.baidu.com/view/1340422.htm)。

---------------------------------------我是华丽的分界线--------------------------------------------

一开始的时候,为了避免麻烦的除法和取模,我想用更相减损法(虽然辗转相除法快很多)。
更相减损法简介:a和b为操作数。(设a=48,b=30)
(1)把a和b的2的因数都约掉(不约也没事,但这样更快)。如:变成a=24,b=15。
(2)若a>b,a=a-b;若b>a,b=b-a;
(3)重复(2)的操作,直到a和b相等。
如: (24,15)->(9,15)->(9,6)->(3,6)->(3,3),即最大公因数为3*2=6。
 
---------------------------------------我是华丽的分界线--------------------------------------------
 
 我好不容易编完、调试好后,发现超时严重哪!
原来,假设有两个数a=1999,b=1。求他们的最大公因数时,按照
更相减损法的步骤,会进行1999次上下!!
即:a=a-b=1999-1=1998;
       a=a-b=1998-1=1997;
      …………
那么我们可以设想,如果a=10000000000000,b=1,我的程序会怎么样?!

---------------------------------------我是华丽的分界线--------------------------------------------

思考再三,我决定编辗转相除法。
首先我们要研究如何编高效的除法运算。二分有很好的效果。
基本思路:设a/b,a的位数是k1位,b的位数k2位。那么我们的答案ans=erfen(l,r)。其中一开始时,l=10000……(共k2-k1位),r=99999……(共k2-k1+1位)。二分的思路就是,如果当前的mid*b>a,向左二分,否则向右二分。
取模运算和除法几乎一样,只是后来要减一下,即a mod b=a-ans*b。
其他原理差不多,为了提高效率,我也先除了2。

---------------------------------------我是华丽的分界线--------------------------------------------
代码一:更相减损法(只过了三个点)
#include
#include

using namespace std;
struct arr
{
  long num,p[101];
  arr() {num=0;memset(p,0,sizeof(p));}
}a,b,ans;
long i,tot;
char u;
bool w[10001];
void change()
{
  long i,t;
  for(i=1;i<=a.num/2;i++){t=a.p[i];a.p[i]=a.p[a.num-i+1];a.p[a.num-i+1]=t;}
  for(i=1;i<=b.num/2;i++){t=b.p[i];b.p[i]=b.p[b.num-i+1];b.p[b.num-i+1]=t;}
}
arr chen(arr a,arr b)
{
  long i,j;
  arr CHEN;
  for (i=1;i<=a.num;i++)
    for (j=1;j<=b.num;j++)
      CHEN.p[i+j-1]+=a.p[i]*b.p[j];
  CHEN.num=a.num+b.num-1;
  for (i=1;i<=CHEN.num;i++)
    if (CHEN.p[i]>9){CHEN.p[i+1]+=CHEN.p[i]/10;CHEN.p[i]%=10;}
  while (CHEN.p[CHEN.num+1]>0)
  {
    CHEN.num++;
    CHEN.p[CHEN.num+1]+=CHEN.p[CHEN.num]/10;
    CHEN.p[CHEN.num]%=10;
  }
  return CHEN;
}
arr add(arr a,arr b)
{
  long i,x=0;arr ADD;
  ADD.num=a.num;if (b.num>ADD.num)ADD.num=b.num;
  for (i=1;i<=ADD.num;i++)
    {
      ADD.p[i]=a.p[i]+b.p[i]+x;
      x=ADD.p[i]/10;ADD.p[i]%=10;
    }
   while (x>0)
   {
     ADD.num++;
     ADD.p[ADD.num]=x%10;
     x=x/10;
   }
  return ADD;
}
arr jian(arr a,arr b)
{
  long i=1,j,k;
  while (i<=b.num)
  {
    if (a.p[i]>=b.p[i])a.p[i]=a.p[i]-b.p[i];
    else
    {
      j=i+1;
      while (a.p[j]==0) j++;
      a.p[j]--;
      for (k=i+1;kb.num) return 1;
  if (a.num0;i--)
  {
    if (a.p[i]>b.p[i]) return 1;
    else if (a.p[i]0;i--)
    {
      a.p[i]=(a.p[i]+x*10);
      x=a.p[i]%2;
      a.p[i]/=2;
    }
    x=0;
    for  (i=b.num;i>0;i--)
    {
      b.p[i]=(b.p[i]+x*10);
      x=b.p[i]%2;
      b.p[i]/=2;
    }
    tot++;
  }
}     
void make()
{
  long temp;long i,j,k,flag=1,x;bool boo=true;
  while (0==0)
  {
    temp=check(a,b);
    if (temp==0) break;
    k++;if (temp==1)
    {
      w[k]=true;
      a=jian(a,b);
    }
    else 
    {
      w[k]=false;
      b=jian(b,a);
    }
  }
  ans=a;
  for (i=2;i<=a.num;i++) a.p[i]=0;
  for (i=2;i<=b.num;i++) b.p[i]=0;
  a.p[1]=1;b.p[1]=1;a.num=1;b.num=1;
  for (i=k;i>0;i--)
  {
    if (w[i]) a=add(a,b);
    else b=add(a,b);
  }
  a=chen(a,b);
  ans=chen(a,ans);
  for (i=1;i<=tot;i++)
  {
    x=0;
    for (j=1;j<=ans.num;j++)
    {
      ans.p[j]=ans.p[j]*2+x;
      x=ans.p[j]/10;
      ans.p[j]%=10;
    }
    if (ans.p[ans.num+1]>0) ans.num++;
  }
}
int main()
{
  scanf("%c",&u);a.num=0;
  while (u!=' ')
  {
    a.num++;a.p[a.num]=u-48;
    scanf("%c",&u);
  }
  b.num=0;
  while (scanf("%c",&u)!=EOF)
  {
    b.num++;b.p[b.num]=u-48;
  }
  change();
  kick();
  make();
  for (i=ans.num;i>0;i--)
    printf("%ld",ans.p[i]);
  return 0;
}
代码二:辗转相除法(全过)  
#include
#include

using namespace std;
struct arr
{
  long num,p[301];
  arr() {num=1;memset(p,0,sizeof(p));}     //定义一个结构体,方便调用。ORZ 任轩笛的教导
}a,b,ans,plusone;
long i,tot;
char u;
void change()                                       //读进来是正的,处理是反的,倒一下。
{
  long i,t;
  for(i=1;i<=a.num/2;i++){t=a.p[i];a.p[i]=a.p[a.num-i+1];a.p[a.num-i+1]=t;}
  for(i=1;i<=b.num/2;i++){t=b.p[i];b.p[i]=b.p[b.num-i+1];b.p[b.num-i+1]=t;}
}
long check(arr a,arr b)                             //检验a和b的大小(高精度)
{
  if (a.num>b.num) return 1;
  if (a.num0;i--)
  {
    if (a.p[i]>b.p[i]) return1;
    else if (a.p[i]0;i--)
    {
      a.p[i]=(a.p[i]+x*10);
      x=a.p[i]%2;
      a.p[i]/=2;
    }
    if (a.p[a.num]==0) a.num--;
    x=0;
    for (i=b.num;i>0;i--)
    {
      b.p[i]=(b.p[i]+x*10);
      x=b.p[i]%2;
      b.p[i]/=2;
    }
    if (a.p[a.num]==0) a.num--;
    tot++;                                                         //tot最多只有300+的
  }
}   
//-------------------------------------分割线--------------------------------------
arr chen(arr a,arr b)                                     //基础的高精度乘法
{
  long i,j;
  arr CHEN;
  for (i=1;i<=a.num;i++)
    for (j=1;j<=b.num;j++)
     CHEN.p[i+j-1]+=a.p[i]*b.p[j];
  CHEN.num=a.num+b.num-1;
  for (i=1;i<=CHEN.num;i++)
    if (CHEN.p[i]>9){CHEN.p[i+1]+=CHEN.p[i]/10;CHEN.p[i]%=10;}
  while (CHEN.p[CHEN.num+1]>0)
  {
    CHEN.num++;
   CHEN.p[CHEN.num+1]+=CHEN.p[CHEN.num]/10;
    CHEN.p[CHEN.num]%=10;
  }
  return CHEN;
}
arr add(arr a,arr b)                                     //基础的高精度加法
{
  long i,x=0;arr ADD;
  ADD.num=a.num;if (b.num>ADD.num)ADD.num=b.num;
  for (i=1;i<=ADD.num;i++)
    {
     ADD.p[i]=a.p[i]+b.p[i]+x;
     x=ADD.p[i]/10;ADD.p[i]%=10;
    }
   while (x>0)
   {
     ADD.num++;
     ADD.p[ADD.num]=x%10;
     x=x/10;
   }
  return ADD;
}
arr jian(arr a,arr b)                                //基础的高精度减法
{
  long i=1,j,k;
  while (i<=b.num)
  {
    if (a.p[i]>=b.p[i])a.p[i]=a.p[i]-b.p[i];
    else
    {
      j=i+1;
      while (a.p[j]==0) j++;
      a.p[j]--;
      for (k=i+1;k1)a.num--;
  return a;
}
//-------------------------------------分割线--------------------------------------
arr CHU(arr a,arr b)                                //高精度除法(二分)
{
  arr l,r,mid,plusone;plusone.p[1]=1;longtemp=check(a,b),i,x;
  l.num=a.num-b.num;r.num=l.num+1;
  for (i=1;i1;i--)
    {
     mid.p[i]=(mid.p[i]+x*10);
      x=mid.p[i]%2;
      mid.p[i]/=2;
    }
    mid.p[1]+=x*10;if (mid.p[1]%2==1)mid.p[1]=mid.p[1]/2+1;else mid.p[1]/=2;
    if (mid.p[mid.num]==0)mid.num--;
    
    temp=check(a,chen(b,mid));
    if (temp==1) l=mid;
    if (temp==-1)r=jian(mid,plusone);
    if (temp==0||check(l,r)==0)break;
  }  
  if (temp==0) return mid;else returnl;
}
arr MOD(arr a,arr b)                                      //高精度求余数
{
  arr l,r,mid,mod,plusone;plusone.p[1]=1;longtemp=check(a,b),i,x;
  l.num=a.num-b.num;r.num=l.num+1;
  if (l.num==0) l.num++;
  for (i=1;i1;i--)
    {
     mid.p[i]=(mid.p[i]+x*10);
      x=mid.p[i]%2;
      mid.p[i]/=2;
    }
    mid.p[1]+=x*10;if (mid.p[1]%2==1)mid.p[1]=mid.p[1]/2+1;else mid.p[1]/=2;
    if (mid.p[mid.num]==0)mid.num--;
    
    temp=check(a,chen(b,mid));
    if (temp==1) l=mid;
    if (temp==-1)r=jian(mid,plusone);
    if (temp==0||check(l,r)==0)break;
  }  
  if (temp==0) mod=jian(a,chen(b,mid));elsemod=jian(a,chen(b,l));
  return mod;
}
arr gcd(arr a,arr b)                                     //辗转相除法
{
  arr mod=MOD(a,b);
  if (mod.num==1&&mod.p[1]==0) returnb;
  return gcd(b,mod);
}
void make()                                           //主要处理场地
{
  long i,j,x;
  if (check(a,b)==1) ans=gcd(a,b);elseans=gcd(b,a);            //求最大公因数
  ans=CHU(a,ans);                                                             //本来是ans=a*b/ans,为了优化,反了一下。
  ans=chen(ans,b);
  for (i=1;i<=tot;i++)                                                           //乘上2的个数
  {
    x=0;
    for (j=1;j<=ans.num;j++)
    {
      ans.p[j]=ans.p[j]*2+x;
      x=ans.p[j]/10;
      ans.p[j]%=10;
    }
    if (x>0) 
     {
       ans.num++;
      ans.p[ans.num]=x;
     }
  }
}
//-------------------------------------分割线--------------------------------------
int main()
{
  scanf("%c",&u);a.num=0;                                //字符串读入处理操作
  while (u!=' ')
  {
    a.num++;a.p[a.num]=u-48;
    scanf("%c",&u);
  }
  b.num=0;
  while (scanf("%c",&u)!=EOF)
  {
    b.num++;b.p[b.num]=u-48;
  }
  change();
  kick();
  make();
  for (i=ans.num;i>0;i--)                                         //倒着输出
    printf("%ld",ans.p[i]);
  return 0;
}


你可能感兴趣的:(vijos题解)