Kodowanie Efektywnych Algorytmów - Ćwiczenia 4


Poprzednie Następne
Temat: Operacje na dużych liczbach

Często zdarza się, że trzeba operować na liczbach większych niż maksymalne wartości, których używamy w kompilatorach. Wówczas na pomoc muszą przyjść operacje pisemne na tzw. Bignumach.
Bignumy, to liczby, które nie mieszczą się w zakresach normalnych zmiennych. Np. liczby o 100 cyfrach. Takie liczby realizuje się poprzez trzymanie "cyfr" (w pewnej bazie, niekoniecznie dziesiętnej) w tablicy, a wykonywane na nich operacje są operacjami nazywanymi w matematyce "pisemnymi" (np. dodawanie pisemne).
Poniżej przykładowa implementacja klasy BigNum (do dużych liczba naturalnych) wraz z komentarzami, którą można wykorzystać do implementacji zadań:
// Implementacja struktury BigNum, realizujacej arytmetyke wielkich liczb

struct BigNum {
// Makro słuzace do eliminowania wiodacych zer
  #define REDUCE() while(len>1 && cyf[len-1]==0) len--;
// Podstawa, przy której wykonywane sa obliczenia oraz liczba zer w podstawie
  static const int BASE=1000000000, BD=9;
// Zmienna len reprezentuje aktualna długosc liczby (liczbe cyfr), a al
// wielkosc zaalokowanej pamieci do przechowywania cyfr liczby
  int len, al;
// Wskaznik do tablicy cyfr liczby
  LL* cyf;
// Konstruktor liczby o wartosci v i zaalokowanej pamieci dla l cyfr
  BigNum(int v=0, int l=2) {
    len=1; al=l;
    cyf = new LL[al];
    REP(x,al) cyf[x]=0;
    cyf[0]=v;
    if (v>=BASE) przen(1);
  }
// Konstruktor, przypisujacy wartosc innej liczby typu BigNum
  BigNum(const BigNum &a) {
    len=al=a.len;
    cyf = new LL[al];
    REP(x,al) cyf[x]=a.cyf[x];
  }
// Destruktor
  ~BigNum(){delete cyf;}
// Funkcja przyjmuje jako parametr zapotrzebowanie na liczbe cyfr i jesli
// zapotrzebowanie jest wieksze od aktualnego rozmiaru tablicy cyfr, to dokonuje
// realokacji
  void Res(int l) {
    if (l>al) {
      l=max(l,2*al);
      LL* n = new LL[l];
      REP(x,l) n[x] = x>=al ? 0 : cyf[x];
      delete cyf;
      cyf = n;
      al = l;
    }
  }
// Funkcja dokonuje przenoszenia do starszych cyfr nadmiaru powstałego na skutek
// wykonywania operacji. Jest to jedyne miejsce w całej strukturze, gdzie
// wykonuje sie ta operacje. Parametr okresla liczbe cyfry, do której nalezy
// wykonac przenoszenie nadmiaru
  void przen(int p) {
    int	x=0;
    for(;x<p || cyf[x]<0 || cyf[x]>=BASE;x++) {
      Res(x+2);
      if(cyf[x]<0){LL i=(-cyf[x]-1)/BASE+1; cyf[x]+=i*BASE; cyf[x+1]-=i;}else
      if(cyf[x]>=BASE){LL i=cyf[x]/BASE; cyf[x]-=i*BASE; cyf[x+1]+=i;}
    }
    len=max(len,x+1);
    REDUCE();
  }
// Od tego miejsca zaczyna sie implementacja operatorów. Przepisywanie tej
// czesci kodu nie jest wymagane - nalezy przepisywac tylko te operatory, z
// których sie korzysta. Niektóre operatory korzystaja z innych - w takich
// przypadkach, przy kazdym operatorze napisane jest, implementacji jakich
// operatorów on wymaga
// Ponizsze makro pozwala skrócic zapis nagłówków operatorów
  #define OPER1(op) bool operator op (const BigNum &a) const
  /* Operatory porownawcze */
  OPER1(==) {
    if(a.len!=len) return 0;
    REP(x,len) if(cyf[x]!=a.cyf[x]) return 0;
    return 1;
  }
  OPER1(<) {
    if(len!=a.len) return len<a.len;
    int x=len-1;
    while(x && a.cyf[x]==cyf[x]) x--;
    return cyf[x]<a.cyf[x];
  }
  OPER1(>) { return a<*this; } /* Wymaga < */
  OPER1(<=) { return !(a<*this); } /* Wymaga < */
  OPER1(>=) { return !(*this<a); } /* Wymaga < */
  OPER1(!=) { return !(*this==a); } /* Wymaga == */

// Operacje dla liczb typu int
  /* Operatory Bignum - Int */
  BigNum &operator=(int a) {
    REP(x,len) cyf[x]=0;
    len=1; cyf[0]=a;
    if (a>=BASE) przen(1);
    return *this;
  }
  void operator+=(int a){cyf[0]+=a; przen(1);}
  void operator-=(int a){cyf[0]-=a; przen(1);}
  void operator*=(int a){REP(x,len) cyf[x]*=a; przen(len);}

  int operator%(int a) {
    LL w=0;
    FORD(p,len-1,0) w=(w*BASE+cyf[p])%a;
    return w;
  }
// Operacje wyłacznie na liczbach typu BigNum
  /* Operatory Bignum - Bignum */
  #define OPER2(op) BigNum& operator op (const BigNum &a)
  OPER2(+=) {
    Res(a.len);
    REP(x,a.len) cyf[x]+=a.cyf[x];
    przen(a.len);
    return *this;
  }
  OPER2(-=) {
    REP(x,a.len) cyf[x]-=a.cyf[x];
    przen(a.len);
    return *this;
  }
  OPER2(*=) {
    BigNum c(0,len+a.len);
    REP(x,a.len) {
      REP(y,len) c.cyf[y+x]+=cyf[y]*a.cyf[x];
      c.przen(len+x);
    }
    *this=c;
    return *this;
  }


  OPER2(=) {
    Res(a.len);
    FORD(x, len-1, a.len) cyf[x]=0;
    REP(x,a.len) cyf[x]=a.cyf[x];
    len=a.len;
    return *this;
  }
// Operatory słuzace do wczytywania i wypisywania liczb
// Funkcja przypisuje liczbie BigNum wartosc liczby z przekazanego wektora,
// zapisanej przy podstawie p
  /* OPERATORY DO WCZYTYWANIA - WYPISYWANIA */
  void read(const vector<int> &v,int p) { /* Wymaga += (int), *= (int) */
    *this=0;
    FORD(x,v.size()-1,0) {
      *this *= p;
      *this += v[x];
    }
  }
// Funkcja przypisuje liczbie BigNum wartosc liczby z napisu zapisanego przy
// podstawie 10
  BigNum &operator=(string a) { /* Wymaga = (int) */
    int s=a.length();
    *this=0;
    Res(len=s/BD+1);
    REP(x,s) cyf[(s-x-1)/BD]=10*cyf[(s-x-1)/BD]+a[x]-'0';
    REDUCE();
    return *this;
  }
// Funkcja wypisuje wartosc liczby BigNum zapisanej przy podstawie 10
  void write() const {
    printf("%d", int(cyf[len-1]));
    FORD(x,len-2,0) printf("%0*d", BD, int(cyf[x]));
  }
// Funkcja wypisuje do przekazanego bufora wartosc liczby zapisanej przy
// podstawie 10
  void write(char *buf) const {
    int p = sprintf(buf, "%d", int(cyf[len-1]));
    FORD(x,len-2,0) p += sprintf(buf+p, "%0*d", BD, int(cyf[x]));
  }

  /* Inne operatory */
// Operator przesuniecia w prawo o n cyfr w prawo  
  BigNum &operator<<=(int ile) {
    if(cyf[0]==0 && len==1) return *this;
    Res(len+ile);
    FORD(x,len-1,0) cyf[x+ile]=cyf[x];
    REP(x,ile) cyf[x]=0;
    len+=ile;
    return *this;
  }
// Operator przesuniecia w prawo o n cyfr w lewo
  BigNum &operator>>=(int ile) {
    if(ile>=len) ile=len;
    REP(x,len-ile) cyf[x]=cyf[x+ile];
    FOR(x,len-ile,ile) cyf[x]=0;
    len-=ile;
    if(len==0) len=1;
    return *this;
  }

  BigNum sqrt() { /* Wymaga <, += (BigNum), *= (BigNum), <<= (int), + (BigNum), * (BigNum), << (int) */
    int n=(len+1)/2;
    BigNum a(0,n), sq;
    FORD(i,n-1,0) {
      int l=0, r=BASE-1;
      while(l<r) {
        int m=(l+r+1)/2;
        if (*this < sq+(a*2*m<<i)+(BigNum(m)*m<<2*i))
        r=m-1;
        else
        l=m;
      }
      sq+=(a*2*l<<i)+(BigNum(l)*l<<2*i);
      a.cyf[i]=l; a.len=n;
    }
    return a;
  }
// Makra pozwalajace na skrócenie zapisu nagłówków ponizszych operatorów
  #define OPER3(op) BigNum operator op(const BigNum &a) const {BigNum w=*this;  w op ## = a;  return w; }
  #define OPER4(op) BigNum operator op(int a) {BigNum w = *this; w op ## = a; return w; }
  OPER3(+); /* Wymaga += (BigNum) */
  OPER3(-); /* Wymaga -= (BigNum) */
  OPER3(*); /* Wymaga *= (BigNum) */

  OPER4(<<); /* Wymaga <<= (int) */
  OPER4(>>); /* Wymaga >>= (int) */
};
Poniżej przykładowa implementacja klasy Integer (do dużych liczb całkowitych) wraz z komentarzami, którą można wykorzystać do implementacji zadań:
// Implementacja liczb całkowitych bazujaca na typie BigNum
int Signum(int x){return x>0?1:(x==0?0:-1);}
int Abs(int x){return x>=0?x:-x;}
struct Integer{
  BigNum x;
  int sgn; //Znak liczby: -1 dla ujemnych, 0 dla zera, 1 dla dodatnich
// Konstruktor, przyjmujacy jako parametry wartosc bezwzgledna oraz znak
// tworzonej liczby
  Integer(const BigNum &a, const int &znak) {x = a; sgn = znak;}
  Integer(const BigNum &a){x = BigNum(a); sgn = a > BigNum(0);}
// Konstruktor tworzacy zmienna równa liczbie a
  Integer(const int &a = 0){x = BigNum(Abs(a)); sgn = Signum(a);}
// Operator zwraca liczbe o przeciwnym znaku
  Integer operator- () const {return Integer(x, sgn*-1);}
// Operatory porównawcze
  bool operator<(const Integer &b) const{
    if (sgn != b.sgn) return sgn < b.sgn;
    if (sgn == -1) return b.x < x;
    return x < b.x;
  }
  bool operator==(const Integer &b) const{return sgn == b.sgn && x == b.x;}
// Makra pozwalajace na skrócenie zapisu nagłówków operatorów
  #define OPER5(op) Integer operator op (const Integer &b) const
  OPER5(-) {return *this + (-b);}
  OPER5(+) {
    if (sgn == -1) return -(-(*this) + (-b));
    if (b.sgn >= 0) return Integer(x + b.x, min(1, sgn + b.sgn));
    if (x < b.x) return Integer(b.x - x, -1);
    return Integer(x - b.x, x > b.x);
  }
  OPER5(*){ return Integer(x*b.x, sgn*b.sgn);}

  #define OPER6(op) Integer &operator op ## = (const Integer &b) {return *this = *this op b;}
  OPER6(+)
  OPER6(-)
  OPER6(*)

// Funkcja wypisuje liczbe Integer zapisana przy podstawie 10 na
// standardowe wyjscie
  void write() const{if (sgn==-1) printf("-"); x.write();}
};
Poniżej przykładowa implementacja klasy Frac do liczb wymiernych o dużych licznikach i mianownikach wraz z komentarzami, którą można wykorzystać do implementacji zadań:
BigNum NatNWD(const BigNum &a,const BigNum& b){return b==BigNum(0) ? a: NatNWD(b, a%b);}
struct Frac{
  Integer a,b;
  Frac(const Integer &aa=0,const Integer &bb=1){
    a = aa; b = bb;
    if(b<0) { a=-a; b=-b; }
    Integer d = Integer(NatNWD(aa.x,bb.x));
    a/=d; b/=d;
  }
  bool operator<(const Frac&x) const { return a*x.b < x.a*b;}
  bool operator==(const Frac&x) const { return a == x.a && b == x.b;}
  #define OPER7(op) Frac operator op (const Frac &x) const
  OPER7(+){ return Frac(a*x.b+b*x.a,b*x.b); }
  OPER7(-){ return Frac(a*x.b-b*x.a,b*x.b); }
  OPER7(*){ return Frac(a*x.a,b*x.b); }
  OPER7(/){ return Frac(a*x.b,b*x.a); }
  bool isZero(){return a==Integer(0);}
  void write(){a.write();printf("/");b.write();}
  #define OPER8(op) Frac &operator op ## =(const Frac &b) {return *this = *this op b;}
  OPER8(+)
  OPER8(-)
  OPER8(*)
  OPER8(/)
};
Aby powyższe kody działały są niezbędne również nagłówki z tzw. biblioteczki UW:
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <iterator>
#include <string>
#include <vector>
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
#define MP make_pair
#define FOR(v,p,k) for(int v=p;v<=k;++v)
#define FORD(v,p,k) for(int v=p;v>=k;--v)
#define REP(i,n) for(int i=0;i<(n);++i)
#define VAR(v,i) __typeof(i) v=(i)
#define FOREACH(i,c) for(VAR(i,(c).begin());i!=(c).end();++i)
#define PB push_back
#define ST first
#define ND second
#define SIZE(x) (int)x.size()
#define ALL(c) c.begin(),c.end()

Zadania do wykonania

Na sprawdzarkach są do wykonania 3 zadania z operacjami na dużych liczbach: Integer Inquiry, Bishop, Paying in Byteland.