TAKMIČENJA IZ PROGRAMIRANJA


Sva pitanja, predloge ili primedbe u vezi sa takmičenjima iz programiranja možete slati na e-mail:

tak.prog@gmail.com

U toku perioda za žalbe, sve žalbe možete slati na ovaj isti e-mail.

proizvod.cpp    2,242 b      izvorni kod rešenja
proizvod2.cpp    576 b      izvorni kod rešenja
proizvod.checker.c    844 b      program za testiranje
proizvod.tests.rar    115,270 b      test primeri

zadatak: Proizvod

Trgovac Joca je u svojoj radnji uveo sistem multiplikativnih cena. Naime, za svaki artikal postoji osnovna cena i Jocina marža, a kupac je dužan da za artikal plati iznos jednak njihovom proizvodu. Trgovac Joca trenutno vrši popis i postavlja nove cene. Za jedan artikal on ima određeni broj pločica, pri čemu je na svakoj ispisana po jedna cifra. Od tih pločica on želi da sastavi osnovnu cenu i maržu za taj artikal tako da iznos koji kupac mora da plati za njega bude maksimalan. S obzirom da je on sva računanja obavio na papiru, on vas je zamolio da vi na računaru nađete poslednje cifre tog maksimalnog iznosa, na osnovu koga će on proveriti ispravnost svog računanja. I cena i marža moraju da sadrže makar jednu pločicu.

Ulaz:

U prvom redu ulaznog tekstualnog fajla proizvod.in nalaze se dva prirodna broja N i M razdvojenih blanko znakom ( 2 ≤ N ≤ 105, 1 ≤ M ≤ 8 ). N predstavlja broj pločica za dati artikal, a M predstavlja broj poslednjih cifara maksimalnog iznosa koje interesuju trgovca Jocu. U drugom redu nalazi se N dekadnih cifara koje predstavljaju cifre na pločicama. Pre i između cifara u drugom redu ne postoji ni jedan blanko znak.

Izlaz:

U prvi i jedini red izlaznog tekstualnog fajla proizvod.out upisati poslednjih M cifara maksimalnog iznosa koji se za dati artikal može dobiti pomoću zadatih pločica. Ukoliko je broj cifara maksimalnog iznosa manji od M, onda upisati ceo iznos dopunjen vodećim nulama tako da je ukupan broj cifara u ispisu jednak M. Pre i između cifara ne upisivati ni jedan blanko znak.

Primer:

proizvod.in      proizvod.out
4 2
2397
        
16

Objašnjenje:

Najveći iznos se dobija ako je osnovna cena 92, a Jocina marža 73 (ili obrnuto). Tada je iznos 6716, a u ispisu se prikazuju poslednje dve cifre, tj. 16.

Primer:

proizvod.in      proizvod.out
3 3
501
        
050

Objašnjenje:

Najveći iznos se dobija ako je osnovna cena 5, a Jocina marža 10 (ili obrnuto); ili ako je osnovna cena 50, a marža 1 (ili obrnuto). Tada je iznos 50, a u ispisu se prikazuje pun iznos sa dopisanom jednom nulom, tj. 050.

rešenje


Problem opisan u zadatku se može iskazati kao problem sastavljanja dva broja od zadatih cifara, tako da je njihov proizvod maksimalan. Neka su to brojevi A i B i neka se oni mogu napisati kao:

A=anan-1…a1a0 i B=bmbm-1…b1b0

Očigledno je da će u oba broja cifre biti poređane u nerastućem redosledu, posmatrajući počev od cifre najveće težine, odnosno:

anan-1≥…≥a1a0 i bmbm-1≥…≥b1b0

Neka su na raspolaganju cifre (bez gubljenja opštosti možemo ih posmatrati u uređenom redosledu):

ctct-1≥…≥c1c0 (t=n+m+1)

Naš zadatak možemo posmatrati kao određivanje koja cifra pripada kom od dva broja, pošto znamo kako se cifre raspoređuju unutar jednog broja. Radi lakšeg zapisa, uvedimo da oznaka Ai,j predstavlja broj aiai-1...aj+1aj i analognu oznaku Bi,j.

Očigledno, najveća data cifra mora biti cifra najveće težine jednog od ova dva broja. Neka je to broj A. Dakle, ct≥an. Pokušajmo da utvrdimo kom broju pripada cifra ct-1. Postoje dve mogućnosti: ct-1bm ili ct-1an-1. Dokazaćemo da je ct-1≥bm. Pretpostavimo suprotno, tj. da je ct-1an-1. Tada je proizvod brojeva A i B:

AB(1)=(ct10n+ct-110n-1+An-2,0) (bm10m+Bm-1,0)=ctbm10n+m+ct10nBm-1,0+ct-1bm10n+m-1+ct-110n-1Bm-1,0+An-2,0bm10m+An-2,0Bm-1,0

Ako bi cifre an-1 i bm zamenile mesta, dobili bismo proizvod:

AB(2)=(ct10n+bm10n-1+An-2,0) (ct-110m+Bm-1,0)=ctct-110n+m+ct10nBm-1,0+bmct-110n+m-1+bm10n-1Bm-1,0+An-2,0ct-110m+An-2,0Bm-1,0

Razliku ovih proizvoda možemo pisati kao:

AB(2)AB(1)=ct10n+m(ct-1-bm)+10n-1Bm-1,0(bm-ct-1)+An-2,010m(ct-1-bm) =(ct-1-bm) (ct10n+m-10n-1Bm-1,0+10mAn-2,0)

S obzirom na to što je ct-1bm, izraz u prvoj zagradi je nenegativan. Ako je ct≥0, onda je i Bm-1,0≥0 (nijedna cifra u B nije veća od ct), pa je i izraz u drugoj zagradi nenegativan (tačnije jednak 0). Ako je ct > 0, onda je ct10n+m≥10n+m, a pošto iz Bm-1,0 < 10m sledi 10n-1Bm-1,0 < 10n+m-1, tada je ct10n+m-10n-1Bm-1,0>10n+m-10n+m-1 > 0, pa je i u ovom slučaju izraz u drugoj zagradi nenegativan (u ovom slučaju strogo pozitivan). Time smo dokazali da uvek važi:

AB(2)AB(1)≥0

Samo za ct-1=bm važi znak jednakosti. Za ct-1 > bm važi da je AB(2)AB(1) > 0, što je kontradikcija našem izboru lokacije za cifru ct-1. Dakle, potrebno je odabrati ct-1bm.

Pretpostavimo da smo rasporedili cifre ct,...,ck+1 među ciframa an,...,ai+1,bm,...,bj+1 (tk=n – i+mj). Za cifru ck mora važiti ckbj ili ck≥ai. Ukoliko je An,i+1Bm,j+1, prethodni izbor je proizvoljan (zbog simetrije). U suprotnom, bez gubljenja opštosti, pretpostavimo da je An,i+1> Bm,j+1. Dokazaćemo da je tada ckbj. Pretpostavimo suprotno, tj. da je ck≥ai. Tada je proizvod brojeva A i B:

AB(1)=(An,i+110i+1+ck10i+Ai-1,0) (Bm,j+110j+1+bj10j+Bj-1,0) =An,i+1Bm,j+110i+j+2+An,i+1bj10i+j+1+An,i+1Bj-1,010i+1+ckBm,j+110i+j+1+ckbj10i+j+ ckBj-1,010i+Ai-1,0Bm,j+110j+1+Ai-1,0bj10j+Ai-1,0Bj-1,0

Ako bi cifre ai i bj zamenile mesta, dobili bismo proizvod:

AB(1)=(An,i+110i+1+bj10i+Ai-1,0) (Bm,j+110j+1+ck10j+Bj-1,0) =An,i+1Bm,j+110i+j+2+An,i+1ck10i+j+1+An,i+1Bj-1,010i+1+bjBm,j+110i+j+1+bjck10i+j+ bjBj-1,010i+Ai-1,0Bm,j+110j+1+Ai-1,0ck10j+Ai-1,0Bj-1,0

Razliku ovih proizvoda možemo pisati kao:

AB(2)-AB(1)=An,i+110i+j+1(ck-bj)+Bm,j+110i+j+1(bj-ck)+Bj-1,010i(bj-ck)+Ai-1,010j(ck-bj) =(ckbj) (An,i+110i+j+1-Bm,j+110i+j+1 - Bj-1,010i+Ai-1,010j)

S obzirom na to što je ckbj, izraz u prvoj zagradi je nenegativan. S obzirom na to što je An,i+1>Bm,j+1, važi:

An,i+110i+j+1-Bm,j+110i+j+1=(An,i+1-Bm,j+1)10i+j+1≥10i+j+1

S druge strane je Bj-1,0 < 10j, pa je Bj-1,010i < 10i+j. Odatle je

An,i+110i+j+1-Bm,j+110i+j+1-Bj-1,010i > 10i+j+1-10i+j > 0,

pa je izraz u drugoj zagradi pozitivan. Time smo dokazali da uvek važi:

AB(2)AB(1)≥0

Samo za ck=bj važi znak jednakosti. Za ck>bj važi da je AB(2)AB(1) > 0, što je kontradikcija našem izboru lokacije za cifru ck. Dakle, potrebno je odabrati ckbj.

Sada možemo sastaviti algoritam:

	sortirati niz cifara c u opadajući poredak c[t]≥c[t-1]≥…≥c[1]≥c[0];
	A=c[t]t;
	B=c[t-1];
	za svako i od t-2 do 0 uraditi:
		ako je AB, dopisati cifru c[i] broju B sa desne strane;
		u suprotnom, dopisati cifru c[i] broju A sa desne strane;
	D=A * B;
	rezultat=D mod 10^(broj cifara ispisa)
	dopisati odgovarajući broj nula rezultatu sa leve strane

Nakon raspoređivanja cifara ct i ct-1 važi AB i cifra ct-2 će biti dopisana broju B, a potom će cifra ct-3 biti dopisana broju A pošto je broj B veći jer ima veći broj cifara (napomena: A će biti veći ako je ct > 0, a ct-1≥0, ali tada su i sve cifre nadalje jednake 0 i svejedno je kom broju se dopisuju). Nakon raspoređivanja cifara ct-2 i ct-3, brojevi A i B će imati isti broj cifara. Ako posmatramo naredne dve cifre (ct-4 i ct-5), ct-4 će biti dopisana manjem (ili jednakom) broju, a ct-5 onom drugom broju. Na taj način, proces dopisivanja cifara možemo raditi u parovima (osim eventualno poslednje cifre).

Pogodno je da relaciju između brojeva A i B određujemo iterativno nakon svakog para cifara, dakle samo na osnovu dopisanih cifara. To možemo uraditi uvođenjem logicke premenljive jednaki koja ima vrednost true dok god su brojevi A i B jednaki, pri čemu vodimo računa da u slučaju nejednakosti uvek znamo koji je broj veći (recimo A).

Takođe, nije pogodno množenje brojeva A i B, jer oni mogu biti jako veliki i, s obzirom na to što je algoritam množenja velikih brojeva kvadratne složenosti poo broju njihovih cifara, vreme izvršenja ovog algoritma bilo bi jako veliko. Međutim, s obzirom na to što je potrebno odrediti samo zadnjih M≤8 cifara proizvoda, dovoljno je pomnožiti poslednjih M cifara brojeva A i B, odnosno naći poslednjih M cifara tog proizvoda. Zgodno je, prilikom množenja, sve operacije obavljati po modulu 10m. Direktno množenje brojeva AM-1,0 i BM-1,0 premašuje opseg 32-bitnog celobrojnog tipa. To nas navodi na množenje jednog od ova dva broja, recimo AM-1,0, ciframa drugog broja, pri čemu se sve operacije obavljaju po modulu 10m.

Konačno, algoritam se može napisati ovako:

	sortirati niz cifara c u opadajući poredak c[t]≥c[t-1]≥…≥c[1]≥c[0];
	A=c[t];
	B=c[t-1];
	ako je c[t]=c[t-1], jednaki=true;
	u suprotnom, jednaki=false i veciJeA=true;
	za svako i od t-2 do 0 sa korakom 2 uraditi:
		ako je  jednaki=true, uraditi sledeće:
			dopisati cifru c[i] broju A sa desne strane;
			ako je i-1≥0 uraditi:
				dopisati cifru c[i-1] broju B sa desne strane;
				ako je c[i]>c[i-1], jednaki=false;
		u suprotnom (jednaki=false),  uraditi sledeće:
			dopisati cifru c[i] broju B sa desne strane;
			ako je i-1≥0, dopisati cifru c[i-1] broju A sa desne strane;
	ako je nM-1,  ApoModulu=A[M-1,0];
	u suprtnom, ApoModulu=A;
	rezultat=0;
	za svako i od min(M-1, m) do 0 uraditi:
		rezultat=( 10 * rezultat+ApoModulu * B[i,i] ) mod 10^m
	dopisati odgovarajući broj nula rezultatu sa leve strane
fajl: proizvod.cpp
#include <fstream>
#include <vector>
#include <algorithm>

using namespace std;

int N, M;
int rezultat;
vector<int> nizCifara;
vector<int> prviBroj;
vector<int> drugiBroj;

void ucitavanje()
{
  int i;
  char ch;

  ifstream inFile = ifstream("proizvod.in");
  
  inFile >> N >> M;

  do
  {
    inFile >> ch;
  } while ((ch < '0') || (ch > '9'));

  nizCifara.push_back((int)ch - 48);  

  for (i = 0; i < N - 1; i++)
  {
    inFile >> ch;
    nizCifara.push_back((int)ch - 48);
  }

  inFile.close();
}


void obrada()
{
  int i, j;
  int modul = 1;
  int prviBrojPoModulu;
  bool jednaki;
  
  for (i = 0; i < M; i++)
    modul *= 10;

  sort(nizCifara.begin(), nizCifara.end());

  prviBroj.push_back(nizCifara[N - 1]);
  drugiBroj.push_back(nizCifara[N - 2]);

  if (nizCifara[N - 1] == nizCifara[N - 2])
    jednaki = true;
  else
    jednaki = false;

  j = N - 3;
  
  // generisanje jednog i drugog broja od cifara
  while (j >= 0)
  {
    if (jednaki)
    {
      prviBroj.push_back(nizCifara[j]);
      j--;
      if (j >= 0)
      {
        drugiBroj.push_back(nizCifara[j]);
        if (nizCifara[j + 1] > nizCifara[j])
          jednaki = false;
        j--;
      }
    }
    else
    {
      drugiBroj.push_back(nizCifara[j]);
      j--;
      if (j >= 0)
      {
        prviBroj.push_back(nizCifara[j]);
        j--;
      }
    }
  }

  if (prviBroj.size() >= M)
    j = prviBroj.size() - M;
  else
    j = 0;

  prviBrojPoModulu = prviBroj[j];
  for (i = j + 1; i < prviBroj.size(); i++)
    prviBrojPoModulu = 10 * prviBrojPoModulu + prviBroj[i];

  if (drugiBroj.size() >= M)
    j = drugiBroj.size() - M;
  else
    j = 0;
  
  rezultat = 0;
  for (i = j; i < drugiBroj.size(); i++)
    rezultat = (10 * rezultat + prviBrojPoModulu * drugiBroj[i]) % modul;
}

void stampanje()
{
  ofstream outFile = ofstream("proizvod.out");

  int i;
  vector<int> cifreRezultata;

  while (rezultat > 0)
  {
    cifreRezultata.push_back(rezultat % 10);
    rezultat /= 10;
  }

  for (i = 0; i < M - cifreRezultata.size(); i++)
    outFile << 0;
  
  for (i = cifreRezultata.size() - 1; i >= 0; i--)
    outFile << cifreRezultata[i];

  outFile << endl;

  outFile.close();
}

int main()
{
  ucitavanje();
  obrada();
  stampanje();

  return 0;
}
fajl: proizvod2.cpp
#include <iostream>
#include <iomanip>
#include <string>
#include <algorithm>
#include <math.h>
using namespace std;

int main() {

    long long N, d[100000], M, k=0, A[2]={0,0}; string s; bool b=1;

    cin >> N >> M >> s;
    for (int i=0; i<N && ((d[i]=s[i]-'0')||1); i++) {}

    sort(d, d+N); reverse(d,d+N);

    for (long i=0; i<N && ((A[k] = (A[k]*10 + d[i]) % 1000000000L)||1); i++)
        b = !(!(k=1-k) && b && (d[i]!=d[i-1]) && (k=1-k)) && b;

    cout << setfill('0') << setw(M) << (A[0] * A[1] % (long long)pow(10.0,(double)M)) << endl;
}