[C++] Ostatnia niezerowa cyfra silni

calmosc
Użytkownik
Użytkownik
Posty: 41
Rejestracja: 4 cze 2013, o 15:28
Płeć: Mężczyzna
Lokalizacja: Toruń
Podziękował: 8 razy

[C++] Ostatnia niezerowa cyfra silni

Post autor: calmosc »

Cześć, mam za zadanie napisać program wyznaczający to, co w temacie (n<1000). Napisałem algorytm, który moim zdaniem powinien działać, przechowuje ostatnie 4 cyfry, jak liczba po pomnożeniu przez chwilowe n (które rośnie w pętli) przekroczy 10000, to przypisuję jej wartość równą reszcie z dzielenia przez 10000 jej samej. Nie wiem gdzie jest błąd, ale przy oddawaniu zadania w internecie serwis informuje, że została podana błędna odpowiedź (zapewne dla dużych n). Podaję kod:

Kod: Zaznacz cały

ostatnia[c]=1;
scanf("%d",&n);
    for (int a=1;a<=n;a++){
        ostatnia[c]=ostatnia[c]*a;
        while(ostatnia[c]>=10000){
            if(ostatnia[c]%10000==0)
            ostatnia[c]=ostatnia[c]/10000;
            else
                ostatnia[c]=ostatnia[c]%10000;
        }
}
while(ostatnia[c]>=10){
            if(ostatnia[c]%10==0)
                ostatnia[c]=ostatnia[c]/10;
            else
                ostatnia[c]=ostatnia[c]%10;
        }
    }
for (int c=0;c<t;c++){
   printf("%d
",ostatnia[c]);
}
Proszę się nie sugerować tym "[c]" przy zmiennej, po prostu to jest przystosowane pod kilka testów naraz, a nie ma potrzeby wrzucania pętli od tego. Jakby ktoś mnie poinstruował co jest nie tak, byłbym bardzo wdzięczny. Z góry dzięki, pozdrawiam.
matmatmm
Użytkownik
Użytkownik
Posty: 2282
Rejestracja: 14 cze 2011, o 11:34
Płeć: Mężczyzna
Lokalizacja: Sosnowiec
Podziękował: 88 razy
Pomógł: 351 razy

[C++] Ostatnia niezerowa cyfra silni

Post autor: matmatmm »

Wymyśliłem taki algorytm. Niestety działa tylko dla n mniejszych od około 130, bo przechowuję w zmiennej dużą liczbę końcowych niezerowych cyfr (dla n=1000 byłoby to ponad 100 cyfr). Pewnie jest jakiś łatwiejszy sposób.

Kod: Zaznacz cały

#include "stdafx.h"
#include <iostream>
using namespace System;
using namespace std;

long long int potega (int podstawa, int wykladnik)
{
	if(wykladnik==0)
		return 1;
	else
		return podstawa*potega(podstawa, wykladnik-1);

}

int main(array<System::String ^> ^args)
{
	int n;
	long long int cyfry=1;
	int l=0;
	int k=0;
	int m;
	cin >> n;

	while (potega(5,k+1)<=n)
	{
		k++; // Sprawdzamy jaka największa potęga 5 mieści się w n
	}
	for (int i=1;i<=n;i++)
	{
		m=i;
		while( m % 10 == 0)
		{
			m=m/10;  // usuwamy zera z liczby m
		}
		
		for(int j=1 ; j<=k ; j++)
			if(m % potega(5,j)==0)
				l++; // liczymy ile cyfr musimy przechowywać

	}
	for(int a=1; a<=n; a++)
	{
		m=a;
		while( m % 10 == 0)
		{
			m=m/10;  // usuwamy zera z liczby m
		}
		for(int j=1 ; j<=k ; j++)
			if(m % potega(5,j)==0)
				l--; // redukujemy liczbę przechowywanych cyfr
		
		cyfry=cyfry*m;

		while( cyfry % 10 == 0)
		{
			cyfry=cyfry/10;  // usuwamy zera 
		}

		cyfry=cyfry % potega(10,l+1); // usuwamy początkowe cyfry

	}
	cout << cyfry;
    return 0;
}
calmosc pisze: Nie wiem gdzie jest błąd, ale przy oddawaniu zadania w internecie serwis informuje, że została podana błędna odpowiedź (zapewne dla dużych n).
Jaki to serwis?
norwimaj
Użytkownik
Użytkownik
Posty: 5101
Rejestracja: 11 mar 2011, o 16:31
Płeć: Mężczyzna
Lokalizacja: 52°16'37''N 20°52'45''E
Podziękował: 4 razy
Pomógł: 1001 razy

[C++] Ostatnia niezerowa cyfra silni

Post autor: norwimaj »

Wymyśliłem taki algorytm. Niestety działa tylko dla małych n.

Kod: Zaznacz cały

int ostsil(int n) {
   int ost = 1, pia = 0, dwo = 0;
   while (n > 0) {
      int k = n--;
      while (k % 5 == 0) {
         pia++;
         k /= 5;
      }
      while (k % 2 == 0) {
         dwo++;
         k /= 2;
      }
      ost *= k;
      ost %= 10;   
   }
   while (dwo > pia) {
      ost *= 2;
      ost %= 10;
      dwo--;
   }
   return ost;
}
EDIT:
Oczywiście kto chce, może się pobawić w naprawę tego kodu. Wydaje się, że w linii 13 jest łatwy do poprawienia błąd.
Ostatnio zmieniony 29 paź 2015, o 20:12 przez norwimaj, łącznie zmieniany 1 raz.
Awatar użytkownika
JakimPL
Użytkownik
Użytkownik
Posty: 2401
Rejestracja: 25 mar 2010, o 12:15
Płeć: Mężczyzna
Lokalizacja: Katowice
Podziękował: 43 razy
Pomógł: 459 razy

[C++] Ostatnia niezerowa cyfra silni

Post autor: JakimPL »

Jeżeli wymagana jest tylko ostatnia cyfra, proponuję poniższy kod:

Kod: Zaznacz cały

int main(int argc, char** argv)
{
	long int n, j = 1, k = 1;
	std::cin>>n;
	while(j <= n)
	{
		k *= j;
		while(k%10 == 0)
			k = k/10;
		k = k%10;
		j++;
		//std::cout<<k<<" ";
	}
	std::cout<<k;
	return 0;
}
Działa dla dużych liczb.

Mam pomysł na pewne usprawnienie. WIP.
norwimaj
Użytkownik
Użytkownik
Posty: 5101
Rejestracja: 11 mar 2011, o 16:31
Płeć: Mężczyzna
Lokalizacja: 52°16'37''N 20°52'45''E
Podziękował: 4 razy
Pomógł: 1001 razy

[C++] Ostatnia niezerowa cyfra silni

Post autor: norwimaj »

JakimPL pisze: Działa dla dużych liczb.
Rzeczywiście, działa dobrze aż do n=24.
Awatar użytkownika
JakimPL
Użytkownik
Użytkownik
Posty: 2401
Rejestracja: 25 mar 2010, o 12:15
Płeć: Mężczyzna
Lokalizacja: Katowice
Podziękował: 43 razy
Pomógł: 459 razy

[C++] Ostatnia niezerowa cyfra silni

Post autor: JakimPL »

Dla \(\displaystyle{ 10^9}\) algorytm u mnie potrzebuje \(\displaystyle{ 16}\) sekund.
norwimaj
Użytkownik
Użytkownik
Posty: 5101
Rejestracja: 11 mar 2011, o 16:31
Płeć: Mężczyzna
Lokalizacja: 52°16'37''N 20°52'45''E
Podziękował: 4 razy
Pomógł: 1001 razy

[C++] Ostatnia niezerowa cyfra silni

Post autor: norwimaj »

Ale czy zwraca dobre wyniki? Polecam do przemyślenia:

Kod: Zaznacz cały

$> let fact n = product [1..n]
$> fact 24
620448401733239439360000
$> 6 * 25
150
$> 36 * 25
900
$> 936 * 25
23400
$> 3936 * 25
98400
Awatar użytkownika
JakimPL
Użytkownik
Użytkownik
Posty: 2401
Rejestracja: 25 mar 2010, o 12:15
Płeć: Mężczyzna
Lokalizacja: Katowice
Podziękował: 43 razy
Pomógł: 459 razy

[C++] Ostatnia niezerowa cyfra silni

Post autor: JakimPL »

Faktycznie, algorytm do naprawy, trzeba rozbić na dwójki i piątki.
Afish
Moderator
Moderator
Posty: 2828
Rejestracja: 15 cze 2008, o 15:45
Płeć: Mężczyzna
Lokalizacja: Seattle, WA
Podziękował: 3 razy
Pomógł: 356 razy

[C++] Ostatnia niezerowa cyfra silni

Post autor: Afish »

calmosc
Użytkownik
Użytkownik
Posty: 41
Rejestracja: 4 cze 2013, o 15:28
Płeć: Mężczyzna
Lokalizacja: Toruń
Podziękował: 8 razy

[C++] Ostatnia niezerowa cyfra silni

Post autor: calmosc »

Mój kod był prawie dobry, wystarczyło lepiej pilnować tych zer (nie dzielić przez 10000, a 10), tutaj poprawny kod:

Kod: Zaznacz cały

#include <cstdio>
#include <cstdlib>

using namespace std;

int main()
{
    int n,ostatnia=1,c=0;
	scanf("%d",&n);
    for (int a=1;a<=n;a++){
        ostatnia=ostatnia*a;
        while(ostatnia>=10000){
            if(ostatnia%10==0)
            	ostatnia=ostatnia/10;
            else
                ostatnia=ostatnia%10000;
        }
     }
     while(ostatnia>=10){
            if(ostatnia%10==0)
                ostatnia=ostatnia/10;
            else
                ostatnia=ostatnia%10;
     }
   printf("%d
",ostatnia);

return 0;
}
Dzięki za zainteresowanie, a stronka, o której mówiłem to spoj.com. Pozdrawiam.
norwimaj
Użytkownik
Użytkownik
Posty: 5101
Rejestracja: 11 mar 2011, o 16:31
Płeć: Mężczyzna
Lokalizacja: 52°16'37''N 20°52'45''E
Podziękował: 4 razy
Pomógł: 1001 razy

[C++] Ostatnia niezerowa cyfra silni

Post autor: norwimaj »

calmosc, większość wpisów tutaj miała raczej humorystyczny charakter, ale widzę, że potraktowałeś to poważnie, więc trzeba Cię teraz wyprowadzić z błędu. Zatem:
1. Rozwiązanie zwracające ujemny wynik już dla n=215586 nie jest poprawne. Pomyśl jeszcze, bo stać Cię na więcej.
2. Czas \(\displaystyle{ O(n)}\) nie jest imponujący. Zdaje się, że Afish wkleił jakiś ciekawy link, więc jeśli sam nic lepszego nie wymyślisz, to możesz tam zajrzeć.
calmosc
Użytkownik
Użytkownik
Posty: 41
Rejestracja: 4 cze 2013, o 15:28
Płeć: Mężczyzna
Lokalizacja: Toruń
Podziękował: 8 razy

[C++] Ostatnia niezerowa cyfra silni

Post autor: calmosc »

Tak właśnie trochę mi tu śmierdziało. ;d Zdaję sobie sprawę z tego, że są szybsze algorytmy i działające dla większych n (stronkę podrzuconą przez Afisha widziałem wcześniej), no ale muszę zająć się też innymi problemami, a jestem leniwy i średnio chce mi się udoskonalać jeden program kosztem paru innych zadań, może później jeszcze do tego wrócę. Pozdrawiam i gratuluję, dałem się nabrać.
norwimaj
Użytkownik
Użytkownik
Posty: 5101
Rejestracja: 11 mar 2011, o 16:31
Płeć: Mężczyzna
Lokalizacja: 52°16'37''N 20°52'45''E
Podziękował: 4 razy
Pomógł: 1001 razy

[C++] Ostatnia niezerowa cyfra silni

Post autor: norwimaj »

Skoro nie chcesz się już w to bawić, to trudno. Sam dokończę. Jeśli w moim rozwiązaniu zastąpimy nieszczęsną linię ost *= k; przez ost *= (k % 10);, to uzyskamy rozwiązanie zwracające poprawny wynik nawet dla n=INT_MAX, ale szybkość takiego rozwiązania nie jest porywająca.


Spróbujmy rozwiązać analogiczne zadanie w systemie piątkowym. Żeby otrzymać liczbę \(\displaystyle{ n!}\) bez końcowych zer w systemie piątkowym, wystarczy uciąć końcowe zera każdemu czynnikowi w iloczynie \(\displaystyle{ 1\cdot2\cdot\ldots\cdot n,}\) bo \(\displaystyle{ 5}\) jest liczbą pierwszą. Na przykład dla \(\displaystyle{ n=113_5}\) mamy iloczyn:

\(\displaystyle{ 1_5\cdot2_5\cdot3_5\cdot4_5\cdot1{\gray{0}}_5\cdot\\
11_5\cdot12_5\cdot13_5\cdot14_5\cdot2{\gray{0}}_5\cdot\\
21_5\cdot22_5\cdot23_5\cdot24_5\cdot3{\gray{0}}_5\cdot\\
31_5\cdot32_5\cdot33_5\cdot34_5\cdot4{\gray{0}}_5\cdot\\
41_5\cdot42_5\cdot43_5\cdot44_5\cdot1{\gray{00}}_5\cdot\\
101_5\cdot102_5\cdot103_5\cdot104_5\cdot11{\gray{0}}_5\cdot\\
111_5\cdot112_5\cdot113_5.}\)


Z liczb w pierwszych czterech kolumnach dostaniemy \(\displaystyle{ (1\cdot2\cdot3\cdot4)^6\cdot(1\cdot2\cdot3)}\) modulo \(\displaystyle{ 5,}\) a z pozostałych po obcięciu jednego zera:

\(\displaystyle{ 1_5\cdot2_5\cdot3_5\cdot4_5\cdot1{\gray{0}}_5\cdot\\
11_5.}\)


Liczba \(\displaystyle{ (1\cdot2\cdot3\cdot4)^p}\) przystaje do \(\displaystyle{ 1}\) lub \(\displaystyle{ 4}\) modulo \(\displaystyle{ 5,}\) w zależności od parzystości \(\displaystyle{ p.}\)

Mamy więc rozwiązanie w systemie piątkowym:

Kod: Zaznacz cały

int ostatnia5(int n) {
   int wynik = 1;
   while (n > 0) {
      for (int k = n % 5; k > 1; k--) wynik *= k;
      n /= 5;
      if (n % 2) wynik *= 4;
      wynik %= 5;
   }
   return wynik;
}
Zastanówmy się teraz, jak to się ma do rozwiązania wyjściowego problemu. Niech \(\displaystyle{ p}\) i \(\displaystyle{ q}\) oznaczają odpowiednio krotność liczby \(\displaystyle{ 5}\) i \(\displaystyle{ 2}\) w liczbie \(\displaystyle{ n!}\) Znaleźliśmy ostatnią cyfrę piątkową takiego \(\displaystyle{ x,}\) że \(\displaystyle{ n!=5^p\cdot x.}\) Niech \(\displaystyle{ y}\) będzie taką liczbą, że \(\displaystyle{ n!=5^p\cdot2^q\cdot y=10^p\cdot(2^{q-p}\cdot y).}\) Mamy równość:

\(\displaystyle{ 2^{q-p}\cdot y = 2^{-p}\cdot x,}\)

co pozwala nam na znalezienie reszty z dzielenia przez \(\displaystyle{ 5}\) liczby \(\displaystyle{ 2^{q-p}\cdot y.}\) A reszta z dzielenia tej liczby przez \(\displaystyle{ 2}\) na ogół jest równa \(\displaystyle{ 0,}\) więc łatwo możemy znaleźć resztę z dzielenia przez \(\displaystyle{ 10,}\) czyli odpowiedź do zadania.

Kod: Zaznacz cały

int krotnosc5(int n) {
   int wynik = 0;
   while (n /= 5) wynik += n;
   return wynik;
}

int potegamod5(int k, int p) {
   int wynik = 1;
   p %= 4;
   while (p-- > 0) wynik *= k;
   return wynik;
}

int ostatnia(int n) {
   if (n < 2) return 1;
   else {
      int wynik = ostatnia5(n) * 
                  potegamod5(3,krotnosc5(n));
      if (wynik % 2 != 0) wynik += 5;
      return wynik % 10;
   }
}
ksisquare
Użytkownik
Użytkownik
Posty: 132
Rejestracja: 1 cze 2012, o 07:04
Płeć: Mężczyzna
Lokalizacja: Polska
Pomógł: 15 razy

[C++] Ostatnia niezerowa cyfra silni

Post autor: ksisquare »

Kod: Zaznacz cały

int ld(int n){
        int q, t, x, z, ai;
        q=0; t=0; x=0;
        if( ((n%5)&1)==0 ) t=n%5;
        n /= 5;
        while(n){
                ai=n%5; n/=5;
                q+=ai; x+=q;
                if((ai&1)==0)
                        t += ai; }
        z = (x+t/2)%4; 
        if( z==0 ) return 6;
        else return 1<<z;}
ODPOWIEDZ