Działania na wielkich liczbach - wzorcówki

0

Witam serdecznie ;-)
Jestem początkującym programistą (że tak się nazwę ;] ) i spotkałem się z problemem działań na dużych liczbach, takich jak np 10^200. Słyszałem oczywiście , że w takich sytuacjach należy zaimplementować własną arytmetykę i wykorzystać szkolną metodę działań "pisemnych". Próbowałem, próby - kończyły się oczywiście, że fiaskiem. Tak to już ze mną jest, że jak nie zobaczę wzorcowego kodu to nie będę tego wiedział. Proszę więc, żeby ktoś przytoczył przejrzyste kody np takich zadań:

--wczytaj n (liczba całkowita, załóżmy zmieści się w int )
--wczytuj kolejno n liczb i sumuj je ze sobą (olbrzymie oczywiście liczby)
--wypisz wynik na standardowe wyjście

Kolejne kody tak samo, tylko że zamiast sumować, powinny odejmować, mnożyć, dzielić.
Za którykolwiek z nich będę niezmiernie wdzięczny ;-)
Z góry dziękuję i pozdrawiam.

0

dodawanie, metoda szkolna, doslownie, lopatologicznie:

#include <iostream>
#include <string>

inline unsigned int zAsciiNaCyfre(char c) { return c - '0'; }
inline char zCyfryNaAscii(unsigned int d) { return d + '0'; }

inline char dodaj(char cyfraL, char cyfraP, unsigned int& przeniesienie)
{
    unsigned int lewa = zAsciiNaCyfre( cyfraL );
    unsigned int prawa = zAsciiNaCyfre( cyfraP );
    unsigned int wynik = lewa + prawa + przeniesienie;

    przeniesienie = wynik / 10;
    return zCyfryNaAscii( wynik % 10 );
}

int main()
{
    using namespace std;

    std::string pierwsza, druga, wynik;

    getline(cin, pierwsza);
    getline(cin, druga);

    unsigned int przeniesienie = 0;
    size_t pozL=pierwsza.size(), pozR=druga.size();

    while(pozL > 0 && pozR > 0)
        wynik.push_back( dodaj(pierwsza[--pozL], druga[--pozR], przeniesienie) );

    while(pozL > 0)
        wynik.push_back( dodaj(pierwsza[--pozL], '0', przeniesienie) );

    while(pozR > 0)
        wynik.push_back( dodaj('0', druga[--pozR], przeniesienie) );

    if(przeniesienie)
        wynik.push_back( zCyfryNaAscii(przeniesienie) );

    for(size_t z=wynik.size(); z>0; )
        cout << wynik[--z];

    cout << endl;
}
0

vlong

0

A ma ktoś coś podobnego, tylko że teraz chodzi mi o wyciąganie silni z liczby typu 10^10. Jeśli można to wolałbym w C

0
Lockheed napisał(a)

...silni z liczby typu 10^10 ..

Czy zdajesz sobie sprawę jak wielka to liczba? Ze wstępnych obliczeń wychodzi mi, że dokładny wynik zajął by około 1010bajtów! Czyli, na razie żaden współczesny PC nie pomieści wyniku w RAMie (niewiele brakuje), a co dopiero policzyć to w rozsądnym czasie.

0

to w takim razie jak program ma obliczyć wzór z danej n, gdzie n maksymalne wynosi 10^9.
Wzór wygląda następująco:
1 + C(n,2) + C(n,4)
gdzie C(n,m) to:
n! / (n-m)! * m!

0

Przez dodawanie i rekurencję

C(n+1,k) = C(n,k-1)+C(n,k)
C(n,0)=1
C(n,1)=n

BTW, jak być miał obliczyć 100000!/99999!, to byś liczył silnie w liczniku, silnie w mianowniku i dzielił? Ja bym skorzystał z definicji silni i napisał 100000!/99999!=100000
pozdrawiam

0

najwieksza silnie jaka tu widze to m!, bo reszte mozesz tak zapisac:
C(n,m) = [n*(n-1)...(n-m+1)]*m! a samo [....] ma tyle samo multiplikacji co m!.

0

@mwili, to niewiele pomoże jeśli m=n/2. Trzeba by się jeszcze bawić w rozkładanie na czynniki i skracanie.

0

ok, zakladajac, ze m <<<<< n , a w podanym przykladzie m = 2 albo 4 ;)

0

mwili ładnie policzył, że:
user image
Jeśli m>n/2 to możemy przyjąć m=n-m a wynik się nie zmieni.

Przyszedł mi pomysł do głowy jak można to maksymalnie poskracać. Należy każdy czynnik po kolei rozłożyć na czynniki pierwsze. Czynniki zapisywać w tablicy, gdzie indeks komórki to kolejny indeks czynnika w tablicy liczby pierwszych. Każdy czynnik z licznika zwiększy wartość komórki o 1, z mianownika zmniejszy. Na samym końcu w tablic powinny nam pozostać jedynie liczby dodatnie - ilość mnożeń jaką musimy wykonać danym czynnikiem pierwszym. Zademonstruje na przykładzie:

pierwsze[] = { 2, 3, 5, 7, ... } - tablice tą powiększamy w miarę potrzeb podczas działania programu.
czynniki[] = { 0 , 0, 0, 0, ... } - tablica na czynniki pierwsze, powiększamy o zerowe komórki w miarę potrzeb, indeks komórki w tej tablicy odpowiada indeksowi czynnika w tablicy liczb pierwszych.
Aby uniknąć realokacji pamięci, rozmiary tablicy możemy oszacować, musimy przybliżyć ilość liczb pierwszych z zakresu od 1 do n (istnieją do tego funkcje matematyczne, np. punkcja PI) i dodać jakiś zapas.

n = 10, m = 6
(n po m) = 78910 / 1234

licznik = 78910
mianownik = 2
3*4

mianownik - odejmujemy

Rozkładamy 2 na czynniki pierwsze, dostajemy 2, czynniki = { -1, 0, 0, 0 }
Rozkładamy 3 na czynniki pierwsze, dostajemy 3, czynniki = { -1, -1, 0, 0 }
Rozkładamy 4 na czynniki pierwsze, dostajemy 2^2, czynniki = { -3, -1, 0, 0 }

teraz licznik - dodajemy

Rozkładamy 7 na czynniki pierwsze, dostajemy 7, czynniki = { -3, -1, 0, 1 }
Rozkładamy 8 na czynniki pierwsze, dostajemy 2^3, czynniki = { 0, -1, 0, 1 }
Rozkładamy 9 na czynniki pierwsze, dostajemy 3^2, czynniki = { 0, 1, 0, 1 }
Rozkładamy 10 na czynniki pierwsze, dostajemy 2*5, czynniki = { 1, 1, 1, 1 }

Wynik to 21315171 = 2357.

Dodatkowo warto pamiętać niektóre rozkłady (na tyle na ile nam pamięć pozwoli). Przyspieszy to obliczenia. Bo jeśli rozkładając jakaś liczbę rozłożymy ją na liczbę już wcześniej rozłożoną to możemy skorzystać z już wcześniej wyznaczonego rozkładu.
Np. rozkładamy 30=215=235, zapamiętujemy. Gdzieś później rozkładamy 120, dostajemy 120=260=2230 i tu skorzystamy z zapamiętanej 30-stki 120=2230=22235

Rozkład na czynniki pierwsze:

#include <iostream> //cin, cout
#include <cmath> //sqrt
#include <cstdlib> //system

using namespace std;

//uwaga, 3-ka jest jeszcze nie wyznaczona, jej obecność w tablicy jest wymaga do
//prawidłowego dziłania funkcji nastepnaPierwsza podczas jej pierwszego wywołania
unsigned pierwsze[300] = { 1, 3 };
unsigned iloscPierwszych = 1;

//funkjca wyznacza kolejna liczbe pierwsza
unsigned nastepnaPierwsza()
{
   unsigned i;
   //sprawdzamy kolejne liczby nieparzyste wieksze od ostatniej liczby pierwszej
   for(unsigned liczba = pierwsze[iloscPierwszych - 1] + 2;; liczba += 2)
   {
      bool czy_pierwsza = true;
      unsigned root = sqrt((double)liczba);
      //sprawdzamy, czy liczba dzieli sie bez reszty przez ktorakolwiek z wyznaczonych
      //liczb pierwszych z pominieciem dwojki bo wiemy ze liczba jest nieparzysta
      //sprawdzane sa liczby pierwsze mniejsze od pierwiastka kwadratowego z liczby
      for(i = 1; pierwsze[i] <= root; i++)
      {
         //jesli traimy na czynnik pierwszy to przerywamy
         //petle zaznaczajac, ze liczba nie jest pierwsza
         if((liczba % pierwsze[i]) == 0) { czy_pierwsza = false; break; }
      }
      //jesli liczba jest liczba pierwsza to dopisujemy ja do tablicy i przerywamy funkjce
      if(czy_pierwsza)
      {
         pierwsze[iloscPierwszych] = liczba;
         iloscPierwszych++;
         return liczba;
      }
   }
}

int main(int argc, char* argv[])
{
   unsigned liczba, pierwsza = 2;
   for(;;)
   {
      cout << "Podaj dowolna liczbe naturalna mniejsza od 1000: ";
      cin >> liczba;
      if(liczba < 1000) break;
      cout << "Podana liczba jest zbyt duza" << endl;
   }
   cout << "Kolejne czynniki pierwsze podanej liczby to:" << endl;

   //bedziemy dzielic liczbe przez jej czynniki pierwsze, az stanie sie jedynka
   while(liczba > 1)
   {
      //dopuki rozkladana liczba dzieli sie przez liczbe pierwsza
      if((liczba % pierwsza) == 0)
      {
         cout << pierwsza <<  ", "; //wypisujemy czynnik pierwszy
         liczba /= pierwsza; //i dzielimy przez niego liczbe
      }
      //jesli rozkladana liczba nie dzieli sie przez liczbe
      //pierwsza to wyznaczamy nastepna liczbe pierwsza
      else pierwsza = nastepnaPierwsza();
   }
   cout << endl;
   system("PAUSE");
   return 0;
}
0

Upieram się przy tym, że moje rozwiązanie jest prostsze i szybsze (nie ma żadnego mnożenia)

unsigned C(int n,int m)
{
  if(n==1) return 1;
  if(m==0) return 1;
  if(m==n) return 1;
  return C(n-1,m-1)+C(n-1,m);
}
0

Przy 109 rekurencja odpada, za mały stos. Można by ją zamienić na iterację + własny stos o wielkości 109 węzłów (8GB).

0
Lockheed napisał(a)

to w takim razie jak program ma obliczyć wzór z danej n, gdzie n maksymalne wynosi 10^9.
Wzór wygląda następująco:
1 + C(n,2) + C(n,4)
gdzie C(n,m) to:
n! / (n-m)! * m!
user image
n!/(n-m)!*m! != n!/( (n-m)! * m!) == n!/(n-m)!/m!
chę?

1+c(n,2)+c(n,4) == 1 + n*(n-1)/2 + n*(n-1)(n-2)(n-3)(n-4)/(23*4)

podobie mnie mi się pomysł z rozkładem na czynniki, ale to chyba nie tu.

Rekurencyjne liczenie C(n,m) jest chyba książkowym przykładem złego rozwiązania.

0
adf88 napisał(a)

unsigned pierwsze[300] = { 1, 3 };

300 to trochę mało, pozwoli na rozkład liczb nie większych od 3948169.
Dla 10^9 potrzebna będzie lista 3402 liczb.

Ale i tak zostaje problem wielkich liczb, np:
C(10^9,4)=41666666416666667124999999750000000=
=27 * 33 * 5^9 * 37 * 71 * 691 * 2251 * 6257 * 333667 * 723589
i czy ilość koniecznych obliczeń nie wzrośnie?

0

Rozkład to tylko przykładowy programik.

adf88 napisał(a)

pierwsze[] = { 2, 3, 5, 7, ... } - tablice tą powiększamy w miarę potrzeb podczas działania programu.
czynniki[] = { 0 , 0, 0, 0, ... } - tablica na czynniki pierwsze, powiększamy o zerowe komórki w miarę potrzeb, indeks komórki w tej tablicy odpowiada indeksowi czynnika w tablicy liczb pierwszych.
Aby uniknąć realokacji pamięci, rozmiary tablicy możemy oszacować, musimy przybliżyć ilość liczb pierwszych z zakresu od 1 do n (istnieją do tego funkcje matematyczne, np. punkcja PI) i dodać jakiś zapas.

Poza tym trochę odbiegłem od tematu, ale wpadł mi pomysł do głowy to się podzieliłem :)

Tu wystarczy wykonać kilka dzieleń i mnożeń.

i czy ilość koniecznych obliczeń nie wzrośnie?
Co do ilości koniecznych obliczeń - jeśli będziemy pamiętać część wszystkich rozkładów to złożoność jest prawie liniowa nie licząc wyznaczania liczb pierwszych.

0
adf88 napisał(a)

...

   while(liczba > 1)
   {
      //dopuki rozkladana liczba dzieli sie przez liczbe pierwsza
      if((liczba % pierwsza) == 0)
      {
         cout << pierwsza <<  ", "; //wypisujemy czynnik pierwszy
         liczba /= pierwsza; //i dzielimy przez niego liczbe
      }
      //jesli rozkladana liczba nie dzieli sie przez liczbe
      //pierwsza to wyznaczamy nastepna liczbe pierwsza
      else pierwsza = nastepnaPierwsza();
   }


...

Czy nie sprytniej byłoby:

   while(liczba > pierwsza*pierwsza)  //  <=========
   {
      //dopuki rozkladana liczba dzieli sie przez liczbe pierwsza
      if((liczba % pierwsza) == 0)
      {
         cout << pierwsza <<  ", "; //wypisujemy czynnik pierwszy
         liczba /= pierwsza; //i dzielimy przez niego liczbe
      }
      //jesli rozkladana liczba nie dzieli sie przez liczbe
      //pierwsza to wyznaczamy nastepna liczbe pierwsza
      else pierwsza = nastepnaPierwsza();
   }
   if (liczba>1) ....   //<==========

jeżeli coś zostało to jest to liczba pierwsza

0

Sprytniej :)
Jeśli jednak przewidujemy dalsze wyznaczanie liczb pierwszych to trzeba by zachować ich ciągłość.

0

Przyznaję rację, rekurencja jest co prawda prosta ale bardzo zła. Korzystanie ze wzoru C(n,m)=
C(n-1,m-1)+C(n-1,m) jest uzasadnione, i to bardzo, gdy musimy wyznaczyć cały trójkąt Pascala, tzn. wszystkie C(n,m) dla n<=M0.
Trochę OT. C(n,m) można zdefiniować na trzy równoważne sposoby:

  1. jako ilość m-elementowych podzbiorów zbioru n-elementowego
  2. rekurencyjnie
  3. n!/m!(n-m)!
    Z definicji 1 i 2 widać, że C(n,m) jest liczbą całkowitą.
    Jak udowodnić korzystając tylko z definicji 3 (i nic nie wiedząc o definicjach 1,2) że C(n,m) jest liczbą całkowitą ?
0
bogdans napisał(a)

[...]Jak udowodnić korzystając tylko z definicji 3[...]
Iloczyn N kolejnych liczb jest podzielny przez N.

0

mgr.Dobrowolski napisał

Iloczyn N kolejnych liczb jest podzielny przez N.

mógłbyś trochę rozwinąć swoją myśl, mam chyba zaćmienie

0

Iloczyn M kolejnych liczb dzieli się przez M.
A z tego wynika, że także iloczyn większej ilości kolejnych liczb dzieli się przez M
np. C(999, 4):

/999\    996 * 997 * 998 * 999
|   | = -----------------------
\ 4 /     1  *  2  *  3  *  4

996 dzieli się przez 1, to i cały licznik dzieli się przez 1 :-)
996997 dzieli się przez 2, to i cały licznik dzieli się przez 2
996
997*998 dzieli się przez 3 to...

0

Zaćmienie trwa
/999\ 994 * 995 * 996 * 997 * 998 * 999
| | = ----------------------------------------
\ 6 / 1 * 2 * 3 * 4 * 5 * 6
Licznik dzieli się przez 2 więc skracamy
Licznik dzieli się przez 3 więc skracamy
Dochodzimy do 6 i w liczniku nie mamy już sześciu kolejnych liczb.

0

994995 dzieli się przez 2, więc (994995)/2 jest liczbą całkowitą, oznaczmy ją A, wiemy, że A996 dzieli się przez 3, czyli A996/3 jest liczbą całkowitą, itd.

to prowadzi do sprytnego algorytmu liczenia C(n,m)

c=n--
i=2
while i <= m
  c = c*n--/i++

co można poprawić dodając na początku

if (2*m > n) m= n-m

A co do wielkich liczb to warto zobaczyć:
20.6 Arithmetic at Arbitrary Precision 915
na początek w sam raz.

1 użytkowników online, w tym zalogowanych: 0, gości: 1