dimecres, 7 de juny del 2017

Aprendre a programar 3 - Problemes resolts I

Aquí resoldré una sèrie de problemes del Jutge. En resoldré del curs "Learning to program", ja que no estic matriculat a PRO1 aquest any, i els que tenia de quan la feia han desaparegut. No obstant, molts coincideixen, i la llista aquesta és pública. Els enunciats els podeu consultar allà, jo explicaré quin procés segueixo per resoldre'ls, i un codi d'exemple

Si me'n demaneu d'alguna altra llista, evidentment també els puc fer. No obstant, demanaria que em paséssiu el seu codi (Pxxxx, Xxxxx, o similar), de manera que el pugui trobar fàcilment

Introducció:

Suma de tres enters - P41221


Bàsicament ens introdueix 3 números, i ens demana que els sumem. Per tant, hem de declarar els 3 números, sumar-los i mostrar aquest resultat. No té gaire misteri:

#include <iostream>
using namespace std;

int main () {
    int a, b, c;
    cin >>a>>b>>c;
    cout <<a+b+c<<endl;
}

Màxim de tres enters diferents - P52847


Hem d'escriure el número més gran dels tres. Per tant, el que hem de fer és llegir-los, i comparar-los de manera que sapiguem quin és el més gran
#include <iostream>
using namespace std;

int main () {
    int a, b, c;
    cin >>a>>b>>c;
    if (a>b) {
        if (a>c) {
            cout <<a<<endl;
        }
        else {
            cout <<c<<endl;
        }
    }
    else {
        if (b>c) cout <<b<<endl;
        else cout <<c<<endl;
    }
}

Una altra opció, utilitzant funcions ja definides, seria

#include <iostream>
using namespace std;

int main () {
    int a, b, c;
    cin >>a>>b>>c;
    cout <<max(max(a,b),c)<<endl;
}

Temperatures - P15613

Aquí tenim 3 possibilitats. Que faci calor (temperatura>30), fred (temperatura<10) o que s'estigui bé. A més, si la temperatura és >=100, hem d'avisar que l'aigua bulliria, i si és <=0 es gela

#include <iostream>
using namespace std;

int main () {
    int temperatura;
    cin >>temperatura
    if (temperatura>30) {
        cout <<"fa calor"<<endl;
        if (temperatura>=100) cout<<"l'aigua bulliria"<<endl;
    }
    else if (temperatura<10) {
        cout <<"fa fred"<<endl;
        if (temperatura<=0) cout <<"l'aigua es gelaria"<<endl;
    }
    else {
        cout <<"s'esta be"<<endl;
    }
}

L'únic a destacar és que només mirem que l'aigua bulli si sabem que la temperatura és >30 (si és més petita, no serà mai 100), i només mirem si es gela quan la temperatura és <10
Una manera de fer-ho millor seria amb constants per aquests números. Una cosa així:

#include <iostream>
using namespace std;

const int FRED=10;
const int BULL=100;
const int CALOR=30;
const int GEL=0;

int main () {
    int temperatura;
    cin >>temperatura
    if (temperatura>CALOR) {
        cout <<"fa calor"<<endl;
        if (temperatura>=BULL) cout<<"l'aigua bulliria"<<endl;
    }
    else if (temperatura<FRED) {
        cout <<"fa fred"<<endl;
        if (temperatura<=GEL) cout <<"l'aigua es gelaria"<<endl;
    }
    else {
        cout <<"s'esta be"<<endl;
    }
}

Anys de traspàs - P61634

#include <iostream>
using namespace std;

int main () {
    int any;
    cin >>any;
    if (any%4==0) {
        if (any%100!=0 or (any/100)%4==0) cout <<"YES"<<endl;
        else cout <<"NO"<<endl;
    }
    else cout <<"NO"<<endl;
}

Aquest el primer cop que el vaig fer em va costar molt, i això que crec que a la pròpia llista n'hi ha de més difícils. Suposo que perquè l'enunciat és una mica enrevessat.
El codi, bàsicament, mira si l'any és divisible per 4. Si no ho és, ja sabem que no és de traspàs. Si ho és, falta comprovar que no sigui divisible entre 100, o que si ho és, sigui també divisible entre 400. Si no és divisible per 100, ja mostrem YES (lazy evaluation, ni mirem la segona condició). Si és divisible entre 100, mirarem que també ho sigui per 400, si ho és, mostrem sí

Bucles:

Primers nombres - P37500

#include <iostream>
using namespace std;

int main () {
    int n;
    cin >>n;
    for (int i=0;i<=n;++i) {
        cout <<i<<endl;
    }
}

Aquest està pensat per començar a practicar amb el bucle for. Bàsicament, fem un for per recórrer de 0 a n (inclòs), i ho mostrem

Suma de quadrats - P36668

#include <iostream>
using namespace std;

int main () {
    int n;
    cin >>n;
    int suma=0;
    for (int i=1;i<=n;++i) {
        suma+=i*i;
    }
    cout <<suma<<endl;
}

En essència, el mateix que abans. Fem una variable on desar el resultat de la suma, i un bucle on anirem calculant els quadrats i els sumarem.

Primers cubs - P69277

#include <iostream>
using namespace std;

int main () {
    int n;
    cin >>n;
    for (int i=0;i<=n;++i) {
        if (i!=0) cout <<',';
        cout <<i*i*i;
    }
    cout <<endl;
}

En aquest fem similar a l'anterior també. La única cosa que pot semblar estranya és la línia "if (i!=0) cout <<','". Aquesta serveix per una cosa molt simple. Sabem que hem de separar els números amb comes, però que no n'hi ha d'haver cap al principi de tot, ni al final. Hi ha diverses maneres d'evitar que surti aquella, la més simple és mostrar-la al principi de totes les iteracions excepte a la primera

Número del revés en binari - P28754

#include <iostream>
using namespace std;

int main () {
    int n;
    cin >>n;
    if (n==0) cout <<0;
    while (n!=0) {
        cout <<n%2;
        n/=2;
    }
    cout <<endl;
}
Aquesta és la primera opció. La idea és que per convertir a binari a mà, anem fent la divisió entera entre 2, i finalment agafem els residus en ordre invers. Per tant, per tenir el nombre al revés, només cal anar fent els residus i mostrar-los. Quan n==0, ens aturem
Això, no obstant, provoca un problema si ho fem amb un while, que és que si n==0 inicialment, ja no entrem al bucle. Però això es soluciona, o bé amb un if al principi, o bé utilitzant un do while

Comptant as (1) - P97969

#include <iostream>
using namespace std;

int main () {
    char c;
    cin >>c;
    int a=0;
    while (c!='.') {
        if (c=='a') ++a;
        cin >>c;
    }
    cout <<a<<endl;
}
Llegim caràcters fins que trobem un punt, i tenim una variable on comptem quantes 'a' ens han entrat

Mitjana - P78142

#include <iostream>
using namespace std;

int main () {
    cout.setf(ios::fixed); 
    cout.precision(2);
    int i=0;
    double d, suma=0;
    while (cin >>d) {
        suma+=d;
        ++i;
    }
    cout <<suma/i<<endl;
}

Aquest va bé per introduir un parell de conceptes. El primer, com fixar quants decimals tindrà la sortida. Si vols que tingui mida n, poses
cout.setf(ios::fixed);
cout.precision(n);
I, a partir d'aquí, la sortida tindrà n decimals
L'altre concepte que podem veure és el while(cin>>d). Això ja ho vaig explicar, però aquí ho veiem en un cas pràctic. El que fa és que, mentre quedin coses per llegir, segueix llegint. A la que no en quedin, acaba el bucle, i segueix amb la resta. La idea d'això és agafar dades d'un fitxer, però també es pot fer des de la consola, simplement cal posar al final el caràcter End Of File (^D a Linux, ^Z a Windows. Per posar-los, Ctrl+D i Ctrl+Z respectivament). 
La resta del programa ja està, només queda a destacar que, quan dividim un double entre un enter, el resultat és un double. Si fossin 2 enters, el resultat seria un enter
[actualització 24/09/2017]

El campanar de la Torrassa - P19724

Aquest me'l van demanar ahir, perquè es veu que molta gent s'hi ha encallat. Com fins ara, poso el codi, i a sota tota l'explicació de la lògica que hi ha darrere del codi
#include <iostream>

using namespace std;

const int CDIA=484;//En un dia sonen 484 campanades
const int MDIA=24*60;

int main() {
    int h, m, t;
    while (cin >>h>>m>>t) {
        int campanades=CDIA*(t/MDIA);
        t%=MDIA;
        if (m%15!=0) {
            int aux=(1+(m/15))*15-m;
            t-=aux;
            m+=aux;
            if (m==60) {
                m=0;
                ++h;
                if (h==24) h=0;
            }
        }
        while (t>0) {
            if (m==0) {
                if (h==0) campanades+=12;
                else if (h==12) campanades+=100;
                else campanades+=h%12;
                campanades+=4;//els quarts
            }
            else campanades+=m/15;
            m+=15;
            if (m==60) {
                m=0;
                ++h;
                if (h==24) h=0;
            }
            t-=15;
        }
        cout<<campanades<<endl;
    }
}

Primer de tot, voldria deixar clar que jo preferiria fer-ho utilitzant funcions, quedaria molt més maco. Però com que l'estan demanant abans d'haver-les estudiat, toca fer-lo així.
Quan me'l van demanar, em van dir que "s'ha de fer sense bucles". La veritat és que això de sense bucles m'ha semblat molt estrany, ja que només per llegir ja ens cal un bucle. Però suposo que és perquè molta gent té solucions que donen Time Limit Exceeded

Per començar amb el programa, hem de tenir en compte que cada dia sonen 484 campanades. Això ho podem calcular a mà, o fer-nos un petit codi que ho calculi. I sabem que un dia són 24*60 minuts. Amb això, podem saber quantes campanades sonaran pels dies sencers. És a dir, si tenim 14450 minuts, això són 10 dies sencers i 50 minuts més. Per tant, podem saber que sonaran 4840 pels dies sencers, i les que sonin pels 50 minuts

Un cop sabem quantes han sonat pels dies sencers, queda saber quantes sonaran en el temps que queda. Com que sabem que només sonen campanades als minuts 0, 15, 30 i 45, mirem quant li falta a m pel número més proper per sobre (mòdul 60), li sumem a m, i li restem a t. I, un cop estem aquí, comença el bucle

En aquest bucle l'únic que hem de fer és mirar quantes sonen a l'hora actual, segons ens ha dit l'enunciat, i saltar a 15 minuts més endavant. 

[Fi actualització 24/09/2017]

Bucles dins de bucles:

El concepte d'aquest tipus de problemes és similar als de bucles. Simplement que enlloc de fer un bucle, en fem dos, un dins de l'altre

Potències - P79817

#include <iostream>
using namespace std;

int main () {
    int a, b;
    while (cin >>a>>b) {
        int r=1;
        for(int i=0;i<b;++i) {
            r*=a;
        }
        cout <<r<<endl;
    }
}

Aquest té dos bucles, però és molt similar als anteriors. El bucle més extern només serveix per llegir els números que elevarem, mentre que és l'intern l'important. Com que sabem que a^b=a*a*a*a*a... b vegades, podem fer-ho igual. Fem un bucle que agafi 1, i el multipliqui per a b vegades, i ja ho tenim. Aquesta és la versió fàcil per calcular potències, que també és la més lenta

Logaritmes - P90133

#include <iostream>
using namespace std;

int main () {
    int b, n;
    while (cin >>b>>n) {
        int i=0,aux=1;
        while (aux*b<=n) {
            aux*=b;
            ++i;
        }
        cout <<i<<endl;
    }
}

Per fer aquest, hem de conèixer la definició del logarítme. Sabem que el logarítme en base b de n és el número al que hem d'elevar b perquè doni n. Per tant, el que fem és anar multiplicant (igual que abans) sense arribar a passar-nos, i comptem quants cops hem multiplicat. 

Triangle - P29973

#include <iostream>
using namespace std;

int main () {
    int n;
    cin >>n;
    for (int i=0;i<n;++i) {
        for (int j=0;j<i+1;++j) {
            cout <<'*';
        }
        cout <<endl;
    }
}

I aquí comencem amb els problemes de dibuixar. Són problemes que, o t'encanten, o els odies, sense terme mig. 
En aquest cas el problema no és excessivament difícil ni hem de tenir en compte massa coses. Quan arribeu a "triangles bufons", "octògons facilets" i altres similars, ja veureu com de difícils poden arribar a ser
Aquí l'únic que hem de fer és dibuixar n files, a la fila 1 li posem un asterisc, a la fila 2, dos asteriscs... Per tant, com que hem començat a comptar pel 0 (podeu començar per l'1 si voleu, cap problema), el for intern fa i+1 iteracions. 

Rombe - P72484

#include <iostream>
using namespace std;

int main () {
    int n;
    cin >>n;
    for (int i=0;i<2*n-1;++i) {
        if (i<n) {//Creixent
            for (int j=0;j<n-i-1;++j) {
                cout <<' ';
            }
            for (int j=0;j<2*(i+1)-1;++j) {
                cout <<'*';
            }
        }
        else {//Decreixent
            for (int j=0;j<i-n+1;++j) {
                cout <<' ';
            }
            for (int j=0;j<2*(2*n-i-1)-1;++j) {
                cout <<'*';
            }
        }
        cout <<endl;
    }
}

Aquest ja és una mica més complicat. El bucle extern no té cap misteri, ja que fem les iteracions que ens diuen. Sabem que, quan i<n, serà quan anem creixent (és a dir, cada fila és més ampla que l'anterior). I quan no, estarem decreixent. 
El cas creixent és fàcil d'explicar. Mostrem n-i-1 espais al principi (compteu-los, funciona així), i després, 2*(i+1)-1 asteriscs (el mateix, es poden comptar). En el cas decreixent, el mateix al revés, cada cop anem fent-los més estrets

Quadrats (1) - P24080

#include <iostream>
using namespace std;

int main () {
    int n;
    bool primer=true;
    while (cin >>n) {
        if (not primer) cout <<endl;
        for (int i=0;i<n;++i) {
            for (int i=0;i<n;++i) {
                cout <<n;
            }
            cout <<endl;
        }
        primer=false;
    }
}

Aquí simplement fem dos fors. Un per les files, i un per les columnes. Després de fer cada fila, mostrem un endl. Podem notar que la variable de control dels dos fors es diu igual. Això ho podem fer i no peta, perquè la del més intern emmascara la de l'extern.
Per últim, faig una variable booleana per saber si és el primer número que hem posat. Si no ho és, mostrem un salt de línia al principi de tot

Quadrats (4) - P35080

#include <iostream>
using namespace std;

int main () {
    int n;
    cin >>n;
    for (int i=0;i<n;++i) {
        if (i!=0) cout <<endl;
        for (int i=0;i<n;++i) {
            for (int j=0;j<n;++j) {
                cout <<(n*i+j)%10;
            }
            cout <<endl;
        }
    }
}

Aquesta és la forma 1 de fer-lo. Sabem que l'element de la fila i j serà (n*i+j)%10, perquè a la fila 0 mostrarem 0, 1, 2..., n-1, a la fila 1 n, n+1, n+2... Podem fer-ho així, en cada moment calculem què hem de mostrar
#include <iostream>
using namespace std;

int main () {
    int n;
    cin >>n;
    for (int i=0;i<n;++i) {
        if (i!=0) cout <<endl;
        int aux=0;
        for (int i=0;i<n;++i) {
            for (int i=0;j<n;++j) {
                cout <<aux;
                ++aux;
                aux%=10;
            }
            cout <<endl;
        }
    }
}

Aquesta és una altra opció, simplement tenim una variable on posem què mostrarem, i l'anem incrementant cada cop

Cuadrats amb distància mínima a diagonals i costats - X13464

#include <iostream>
#include <cmath>

using namespace std;

int main(){
    int n;
    bool primer=true;
    while (cin >>n) {
        if (not primer) cout <<endl;
        for (int i=0;i<n;++i) {
            for (int j=0;j<n;++j) {
                int dist_vert=min(i,n-i-1);
                int dist_horit=min(j,n-j-1);
                int dist_diag1=abs(i-j);
                int dist_diag2=abs(n-i-j-1);
                cout <<min(min(dist_vert,dist_horit),min(dist_diag1,dist_diag2))%10;
            }
            cout <<endl;
        }
        primer=false;
    }
}

Aquest no surt a les llistes, ni a la de "Learning to program", ni a la de PRO1. És un problema d'examen, que em va sortir a mi. He penjat el PDF i els fitxers del programa aquí.
Recordo que gairebé ningú el va saber fer. Ja van ser poques les persones que van arribar a aquest problema, menys encara les que van entendre què demanava, i no diguem ja les que ho van plasmar en un codi. No obstant, vist ara amb perspectiva, no és tan diferent dels de dibuixar (personalment, em semblen més difícils els de dibuixar figures geomètriques).

En essència, aquí hem de veure que, el que hem de fer, és calcular distàncies. El que faig jo és fer-ho separat, perquè quedi més clar (jo sóc el típic que, si he de presentar una línia de 200 caràcters, la presento, però m'odiaríeu).
-Primer calculem la distància vertical (és a dir, com de lluny està de la primera fila, i com de la última). La primera és la 0, per tant, la distància és i-0=i. La última és n-1, per tant, la distància és n-1-i. Agafem la menor de les dues
-Acte seguit la distància horitzontal. El mateix, però amb la j.
-Acte seguit, la distància a la diagonal principal. Aquesta és quan i=j, per tant, la distància serà el valor absolut de i-j. (3,1) està a distància 2 de la diagonal primària, (1,3) també
-Finalment, a la diagonal secundària. En aquesta, sabem que i+j=n-1. Per tant, la distància serà el valor absolut de n-i-j-1

Un cop ja tenim aquestes 4 distàncies, hem d'agafar la mínima de les quatre, i mostrar-la mòdul 10.

Nota: Voldria remarcar que, en un cas així, millor redirigir la sortida a un arxiu i comparar-lo amb la sortida original. El cas més gran que ens donaven aquí era n=40, i posar-te a mirar a ull si dóna bé o no acaba marejant.
Per si algú no recorda com es feia, suposant que els arxius són els d'aquest problema, i que el programa es diu program (a seques, ni extensió ni hòsties, que als sistemes Unix no cal)
$./program <sample-000.inp >sortida.txt
$diff -dB sample-000.cor sortida.txt
$./program <sample-001.inp >sortida.txt
$diff -dB sample-001.cor sortida.txt

Si ha funcionat bé, cap dels diff mostrarà res. Si ho mostra, vol dir que el nostre programa no funciona com ha de funcionar

Funcions:

Aquí simplement posaré la funció que demanin. Ni inclusions, ni main, ni res. Només el que cal. Com que són les nostres primeres funcions, no són problemes gaire estranys. 

Nota: Quan diu "iteratiu", es refereix a que es resol utilitzant bucles

Factorial iteratiu - P57474

int factorial (int n) {
    int r=1;
    for (int i=1;i<=n;++i){
        r*=i;
    }
    return r;
}

Un factorial, com sabem, és el productori des de i=1 fins a n. És a dir, n*(n-1)*(n-2)*...*3*2*1. Sabem també que 0!=1. Per tant, l'únic que cal és fer un for que vagi multiplicant els números

Màxim de dos enters - P57846

int max2(int a, int b) {
    if (a>b) return a;
    return b;
}

Màxim de quatre enters - P73231

int max4(int a, int b, int c, int d) {
    return max2(max2(a,b),max2(c,d));
}

Aquí el que fem és calcular el màxim de a i b, el de c i d, i el màxim d'aquests dos màxims. Important recordar que hem de tenir la funció max2 definida, i abans que la max4. Això és perquè tot el que utilitzem ho hem hagut de declarar abans, i si la funció max2 està declarada a sota, max4 no en coneix l'existència

Valor absolut - P96275

int absolute(int n) {
    if(a>0) return a;
    return -a;
}

Màxim comú divisor iteratiu - P88790

int mcd(int a, int b) {
    int dividend, divisor, residu;
    if (a>b) {
        dividend=a;
        divisor=b;
    }
    else {
        dividend=b;
        divisor=a;
    }
    if (divisor==0) divisor=dividend;
    while (dividend%divisor!=0) {
        residu=dividend%divisor;
        dividend=divisor;
        divisor=residu;
    }
    return divisor;
}

L'únic problema d'aquest exercici és que l'algorisme d'Euclides s'estudia més tard, i toca buscar a Google. 
No he tingut gaire ganes de programar-lo, i com que ja l'havia fet quan feia PRO1, he copiat el codi que vaig fer en aquell moment. És lleig, pesat, i probablement ineficient, com el 99% de codis que es troben a Pastebin

Capicues - P77686

bool es_capicua(int n) {
    int girat=0;
    int copia_n=n;
    while (copia_n!=0) {
        girat*=10;
        girat+=copia_n%10;
        copia_n/=10;
    }
    return girat==n;
}

Aquí, el que faig, és girar n, i comprovar que sigui igual que el girat. Si ho és, vol dir que és capicua, i si no, doncs no. 
Un detall és que faig return girat==n, enlloc de
if (girat==n) return true;
return false;
Això és perquè, al cap i a la fi, girat==n ens dóna un booleà, que és true si són iguals. Podem retornar-lo directament, i està més ben vist

Funció per als anys de traspàs - P95401

bool es_any_de_traspas(int any) {
    return any%4==0 and (any%100!=0 or any%400==0);
}

Dates vàlides - P58459

bool es_data_valida(int d, int m, int a) {
    if (m==1 or m==3 or m==5 or m==7 or m==8 or m==10 or m==12) {
        return d>0 and d<=31;
    }
    else if (m==4 or m==6 or m==9 or m==11) {
        return d>0 and d<=30;
    }
    else if (m==2) {
        if (es_any_de_traspas(a)) {
            return d>0 and d<=29;
        } 
        return d>0 and d<=28;
    }
    return false;
}

No cal explicar gaire tampoc, no? Si el mes és un mes de 31 dies, mirem que el dia estigui entre 1 i 31 inclosos. Si no, si és de 30 dies, el mateix. Si no, si és febrer, depenent de si és any de traspàs. Si no és cap d'aquests mesos, retornem false (no hi ha un mes 0, ni un mes 13, per exemple)


Al proper article, amplio una mica el que hem fet fins ara de funcions

Cap comentari:

Publica un comentari a l'entrada