Skip to content

Aquest repositori conté problemes que he resolt en la preparació per a la ProgramaMe. També pot servir com a manual de preparació per a altres participants. Conté una resolució detallada de diversos exercicis de competicions regionals, a més d'una valoració subjectiva del grau de dificultat, per ajudar als estudiants a triar els que volen practicar

Notifications You must be signed in to change notification settings

blackcub3s/ProgramaMe

Repository files navigation

0. Index

  1. Introducció

  2. Escalfament pre-nadalenc (preparació 2024)

  3. 2023 (Regional Villaviciosa de Odón i Terrassa)

  4. 2023 (Regional de Zaragoza)

  5. 2024 (Regional de València)

1. Introducció

En aquest repositori es resoldran problemes de la competició ProgramaMe. Tots els problemes pujats estan validats amb AC (accepted o acceptat) fent servir el validador de la pàgina web https://aceptaelreto.com/ que implementa solucions als mateixos i compara els outputs amb la sortida dels programes que he fet i he publicat aquí, a menys que s'indiqui el contrari en el comentari del problema (si en algun cas no és acceptat diré quin error s'ha produït i per què no s'ha pogut fer funcionar). Cada problema té al titol un link a la pàgina mencionada, que conté el compilador per fer la validació del codi del qui l'intenti i una còpia de l'enunciat.

2. Escalfament pre-nadalenc (preparació 2024)

Els problemes d'aquesta secció es poden trobar, a més a més de tenir-los en format html fent click al titol de la secció que encapçala cada cada problema, en format PDF a la pàgina de la programaMe o en aquestarxiu pujat al repositori.

Penso que la millor forma de resoldre aquest problema és mitjançant una estructura de dades que permeti emmagatzemar parelles de clau : valor de forma eficient, acumulant així per a cada cas de prova el nombre d'ocurrències -vots- que cada país té (és una parella clau -país- i valor -nombre d'ocurrències-). En Python l'estructura de dades que permet implementar aquesta solució són els diccionaris (dict), i en C++ i Java tenim els HashMap (d'estructura molt més complicada que els diccionaris de Python).

En Java, podem declarar un map d'aquesta manera:

Map<String, int> elMeuMap = new HashMap<String, int>();

Per iterar a través d'un map podem fer servir la sintaxis for-each de la que ens proveeix Java i, un cop tenim cada parell clau:valor podem accedir al seu contingut mitjançant les funcions getKey() i getValue(). Cal fer notar que és necessari declarar cada parell_clauValor com un Map.Entry<String, Integer> i que quan iterem elMeuMap no podem iterarlo de manera com fèiem amb els tipus de dades primitius, sinó que ens cal cridar una funció en l'objecte que conté el nostre map elMeuMap.entrySet(), que retorna un Set o conjunt d'objectes Map.Entry (objectes clau:valor).

Així doncs, la sintaxis per iterar un map quedaria de la següent manera:

for (Map.Entry<String, Integer> parell_clauValor : elMeuMap.entrySet()) {                             
       String clau = parell_clauValor.getKey(); 
       int valor = parell_clauValor.getValue();
} 

Cal també esmentar com crear un nou parell clau:valor dins d'un map, per una banda; i com accedir a un valor a partir d'una clau dins el map, per l'altra. Per aconseguir la creació d'un nou parell farem servir la funció put(): així doncs elMeuMap.put(clau, valor) afegirà un parell clau:valor al diccionari. D'altra banda, per accedir al valor que tingui el map per clau clau farem elMeuMap.get(clau).

Finalment cal esmentar que en aquest problema és necessari l'ús de la funció sc.next() que permet consumir del canal estàndard d'entrada la següent paraula d'una linia d'strings (nextline en canvi consumiria tota una línia i no ens permetria resoldre bé el problema). El problema l'he resolt així:

import java.util.Map;
import java.util.Scanner;
import java.util.HashMap;
/* @author santi @fecha 26 ene. 2024*/
public class ProblemaA {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
//llegeixo cada cas de prova mentre n'hi hagi (condició finalitzacio nVots == 0, dins bucle amb break
while (true) {
Map<String, Integer> paisVots = new HashMap<>(); //declaro un nou hashMap (mapa)
int nVots = sc.nextInt(); //llegeixo nombre de vots esperats (nre strings)
sc.nextLine(); //netejo bufer
//CAS EN QUE S'ACABA LA SEQUÈNCIA
if (nVots == 0)
break;
//LLEGEIXO ELS STRINGS (PAISOS) I ELS CONTO DINS
//CADA VALOR DE LA CLAU CORRESPONENT (PER A UN CAS DE PROVA DONAT)
String pais = "";
for (int i = 0; i < nVots; ++i) {
pais = sc.next(); //llegeixo paraula a paraula
if (paisVots.containsKey(pais)) //el pais ja ha sigut afegit a dins el map (sumem +1 als vots existents)
paisVots.put(pais, paisVots.get(pais) + 1);
else //el pais no existeix dins el map (posem un vot)
paisVots.put(pais, 1);
}
//MIRO EL PAIS GUANYADOR O SI HI HA EMPATS (EL HASMAP ESTÀ ORDENAT)
//I SI HI HA UN SOL PAIS NO CAL MIRAR EL HASH
if (paisVots.size() == 1)
System.out.println(pais);
else {
//primera posicio es el pais amb mes vots. Segona posicio es el
//segon pais amb mes vots. Si coincideixen hi ha empat
int maxVots = 0; //sempre hi haurà com a minim un vot aixi que 0 es un enter que podem escollir.
String paisMaxVots = "";
//RECORREM EL MAP PER TROBAR ELS PAISOS MES VOTATS
//BOOLEA empat clau per resoldre els empats (ha COSTAT!)
boolean empat = false;
for (Map.Entry<String, Integer> parell_paisVot : paisVots.entrySet()) {
String paiset = parell_paisVot.getKey();
int votetsPaiset = parell_paisVot.getValue();
//System.out.printf("pais: %s", paiset);
//System.out.printf("| vots: %d\n", votetsPaiset);
if (votetsPaiset > maxVots) {
maxVots = votetsPaiset;
paisMaxVots = paiset;
empat = false;
} else if (votetsPaiset == maxVots)
empat = true;
}
//IMPRIMIM EL RESULTAT PER PANTALLA
if (empat)
System.out.println("EMPATE");
else
System.out.println(paisMaxVots);
}
}
}
}

El problema B implica primer absorbir el primer enter de la seqüència d'entrada (que indica el nombre de casos de prova del mundial que inclou el test). Un cop fet això ja es pot anar a processar cada cas de prova (cada línia). En cada cas de prova es demana senzillament fer una diferència entre la suma dels equips assignats a les sis confederacions del mundial i el nombre d'equips E que participen en el mundial i imprimir-la per pantalla. La meva solució en java (no mostro l'import de l'Scanner, veure arxiu sencer):

public class ProblemaB {
public static void main(String[]args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
sc.nextLine(); //consumeixo bufer
//RECORRO FILES (CADA FILA ÉS CAS DE PROVA D'UN MUNDIAL)
for (int i = 0; i < n; ++i) {
//MIRO EQUIPS QUE TÉ EL MUNDIAL
int nEquips = sc.nextInt();
//RECORRO ELS EQUIPS PER CADA CONFEDERACIÓ I ELS ACUMULO
int sumEquipsConfederacio = 0;
for (int j = 0; j < 6; ++j) {
sumEquipsConfederacio += sc.nextInt();
}
sc.nextLine(); //consumeixo bufer
//IMPRIMEIXO EL NOMBRE D'EQUIPS QUE VAN A LA REPESCA INTERCONTINENTAL
//SI S'ESCAU
System.out.println(nEquips - sumEquipsConfederacio);
}
}
}

La meva solució en C++, que no requereix netejar el búfer després de llegir tipus de dades primitius i requereix menys codi per fer-lo funcionar 1.

#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
//RECORRO FILES (CADA FILA ÉS CAS DE PROVA D'UN MUNDIAL)
for (int i = 0; i < n; ++i) {
//MIRO EQUIPS QUE TÉ EL MUNDIAL
int nEquips;
cin >> nEquips;
//RECORRO ELS EQUIPS PER CADA CONFEDERACIÓ I ELS ACUMULO
int sumEquipsConfederacio = 0;
int entrada;
for (int j = 0; j < 6; ++j) {
cin >> entrada;
sumEquipsConfederacio += entrada;
}
//IMPRIMEIXO EL NOMBRE D'EQUIPS QUE VAN A LA REPESCA INTERCONTINENTAL SI CAL
cout << nEquips - sumEquipsConfederacio << endl;
}
return 0;
}

Aquest problema no és tant complicat com el problema A. Tot i així, sí que té una petita complicació: cal saber carregar cada un dels casos de prova (nombres) com a strings i accedir a cada un dels digits d'aquests nombres individualment. Per fer aquest accés cal saber passar de char a enter. Hi ha dues formes de fer-ho: una, seria amb la funció int n = Character.getNumericValue(nre.charAt(i));; l'altra seria fer servir una funció comuna que permet convertir un string a enter, i per a fer-ho ens caldrà passar el char a String amb un truquet (concatenar un char amb un String buit ens permet obtenir un string!) i aleshores podrem utilitzar funcions ben conegudes que converteixin d'String a enter sense saber-nos les funcions que converteixen de Char a enter, així: int n = Integer.parseInt(""+nre.charAt(i));.

Aquest exercici també té la dificultat de recórrer les cadenes a l'esquerra i a la dreta del nombre en qüestió (cosa que he disseccionat en sengles funcions fora del main) i de vigilar què és posició parell d'un digit dins el nombre del cas de prova (index senar, si indexem des de zero) o posició senar d'un digit dins el nombre del cas de prova (index parell, si indexem des de zero):

public static int tractaParell(String nre, int i) {
//int n = Character.getNumericValue(nre.charAt(i)); //FUNCIO ESPECIFICA
int n = Integer.parseInt(""+nre.charAt(i)); //ALTERNATIVA GUAY A FUNCIÓ PREVIA
int mesGranEsq = -1;
for (int j = i - 1; j >= 0; --j) {
int a = Character.getNumericValue(nre.charAt(j));
if (a > mesGranEsq)
mesGranEsq = a;
}
return n*2 + mesGranEsq;
}
public static int tractaImparell(String nre, int i) {
//int n = Character.getNumericValue(nre.charAt(i));
int n = Integer.parseInt(""+nre.charAt(i));
int menorDreta = 10;
for (int j = i + 1; j < nre.length(); ++j) {
int a = Character.getNumericValue(nre.charAt(j));
if (a < menorDreta)
menorDreta = a;
}
return n*3 + menorDreta;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int nCasos = sc.nextInt();
//itero als casos de prova
for (int i = 0; i < nCasos; ++i) {
String nombre = sc.next();
//calculo el codi de verificació
int codi = 0;
for (int j = 0; j < nombre.length(); ++j) {
if (j % 2 == 0) //ex. j==0 es posicio 1 (imparell)
codi += tractaImparell(nombre,j);
else
codi += tractaParell(nombre,j);
}
System.out.println(codi);
}
}

Aquest problema no el va enviar ningú quan es va fer el repte de l'escalfament pre-nadalenc (un repte amb més de 900 persones i amb poc temps per resoldre tots els problemes). Era, doncs, un problema visiblement complicat. La solució que proposo aquí funciona per al cas de prova proporcionat a l'enunciat de la web d'aceptaelreto, però es prega al lector prendre la solució amb cautela ja que el programa aquí mostrat dóna error de límit de temps excedit en enviar-lo al servidor de comprovació (TLE -Time limit exceeded-). Entenc que el problema no es produeix perquè la solució programada estigui innerentment mal programada, sino perquè la funció sc.hasNextInt() no funciona com seria d'esperar. No he aconseguit que aturi la sequència d'entrada de valors N, que és incerta ja que depèn del nombre de casos de prova i no ens els informen ni ens donen una senyal d'aturada de la seqüència d'entrada: això fa que el programa es quedi esperant un altre nombre i no s'aturari fins que no entri un caràcter NO numèric (cosa que mai tindrem als casos de prova). Si es pogués corregir aquest aspecte segurament funcionaria.

En la solució que jo proposo s'aconsegueix tenir en compte els casos de prova amb casos particulars com N==1 (un sol esdeveniment per partit, que a la pràctica és estrany, però es podria donar en els tests que facin per comprovar el nostre programa) i N > 1 (més d'un esdeveniment per partit). També tinc en compte quan només es fa una consulta per a un esdeveniment del partit (q == 1), cridant exclusivament a la funció fesSortidaPerConsulta_igual_a_1(); també considero els casos en que q > 1 tot fent ús de la funció fesSortidaPerConsulta_amb_q_superior_a_1(). Es aquesta última funció la que té un comportament un tant complicat: fer una finestra de q espais que es vagi desplaçant d'esquerra a dreta al llarg del vector v mirant el temps que passa entre l'esdeveniment inicial de la finestra i el següent, entre el següent i el tercer, i així successivament fins arribar al que ocupa la posició q (v el fem servir per guardar els diferents moments temporals on es van produint els events durant el partit). Després, aquesta finestra la mourem a la dreta una unitat i repetirem el procés anterior. D'aquest procés iteratiu, de cada finestra, ens guardem en una variable tqMin el temps mínim que passa fins a recorrer els q esdeveniments i la posició inicial iMaxDens per trobar un cop acabem de recórrer tot vamb la finestra l'esdeveniment inicial que dóna peu a la regió amb q esdeveniments amb màxima densitat.

public class ProblemaD {
//PRE: v --> vector amb els temps en que es produeixen events
// q ----> nombre d'events consecutius dels quals volem consultar
// on es dóna la màxima densitat d'ocurrència. CAL QUE(2 <= q <= v.length))
// N ----> N és v.length())
//POST: Sortida demanada pel Canal estàndard de sortida
public static void fesSortidaPerConsulta_amb_q_superior_a_1(int[] v, int q, int N) {
if (N == q) {
System.out.println(v[0]+" "+v[N-1]+" "+q);
} else { //N > q (obvi)
int iMaxDens = -1; //index inicial del vector v on trobem l'agrupació q amb maxima densitat d'events.
int tqMin = Integer.MAX_VALUE;
int j = 0;
//funcionara per a q >= 2 pero n oper a q igual a 1 que caldar canviar
//FINESTRA QUE DESPLAÇA EL BUCLE INTERIOR A LO LLARG DEL VECTOR PER TROBAR EL tqMin i l'iMaxDens
while (j < N - q) {
int tq = 0; //temps que passa desde el primer event trobat fins a q
for (int i = 0; i < q - 1; ++i) {
tq += v[j+i+1] - v[j+i];
}
if (tq < tqMin) {
tqMin = tq;
iMaxDens = j;
}
j = j + 1;
}
//NOTA QUE NO HAGUÉS FUNCIONAT PER A q IGUAL A 1
System.out.println(v[iMaxDens]+" "+v[iMaxDens + q - 1]+" "+q);
}
}
//PRE: v --> vector amb els temps en que es produeixen events
// cal que q valgui sempre 1 (no passem per paràmetre perquè ja ho controlem fora)
// N ----> N és v.length())
//POST: Sortida demanada pel Canal estàndard de sortida
public static void fesSortidaPerConsulta_igual_a_1(int[] v, int N) {
//CAS EN QUE N >= 2 (si N == 1 NO ENTRAÀ AL BUCLE)
boolean valorDuplicat = false;
for (int i = 0; i < N - 1; ++i) {
if (v[i] == v[i+1]) { //event duplicat en un mateix moment (ex entrada i sortida jugadors)
System.out.println(v[i]+" "+v[i+1]+" "+2);
return;
}
}
//CAS EN QUE N >= 1 (perfecte)
if (!valorDuplicat) //no existeix event duplicat
System.out.println(v[0]+" "+v[0]+" "+1);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
//CAL RECORRER DIVERSOS CASOS DE PROVA. OJO QUE A L'INPUT
//ET POSEN NOMÉS UN CAS DE PROVA
while (sc.hasNextInt()) { //NO ES PARA...
int N = sc.nextInt(); //nre events anotats
sc.nextLine(); //buffer netejat
int[] v = new int[N]; //vector d'events i temps
for (int i = 0; i < N; ++i) {
v[i] = sc.nextInt(); //guardo el temps de cada event
sc.nextLine(); //no faig servir la descripcio de l'event, me la menjo.
}
//MIREM NOMBRE DE CONSULTES
int Q = sc.nextInt();
sc.nextLine(); //menjo salt de línia
//RECORREM LES CONSULTES
for (int i = 0; i < Q; ++i) {
int q = sc.nextInt();
sc.nextLine(); //menjo salt línia
if (q == 1)
fesSortidaPerConsulta_igual_a_1(v, N);
else
fesSortidaPerConsulta_amb_q_superior_a_1(v, q, N);
}
//IMPRIMIM LINIES DE FINAL DEL CAS DE PROVA
System.out.println("---");
}
}
}

Per fer aquest problema començem processant els casos de prova encapçalats per les variables enteres N Q en la mateixa línia (que són el nombre d'equips de la fase de grups i la quantitat d'equips classificats, respectivament). En l'inici del plantejament ens podem preguntar com volem parar la lectura del canal estàndard d'entrada quan ja no hi hagi entrades d'enters N Q (haguem consumit tots els casos de prova). No aconsegueixo que el do...while s'aturi quan ja no hi ha cap altre enter pel canal estàndard d'entrada, ja que sc.hasNextInt() no retorna false fins que no rep algo que no sigui un enter, cosa que no passa en l'enunciat del problema perquè deixen el nombre de casos de prova obert i no informen de final de seqüència. Com aconseguir, amb java, que el bucle de lectura de casos de prova acabi? És exactament la mateixa dificultat que tinc a l'exercici D.

De cara a processar cada cas de prova, després d'engollir N i Q ens diuen que tenim $N(N-1)/2$ enfrentaments entre partits. Com a curiositat (podeu passar al següent paràgraf, no és necessari saber-ho per fer el problema), aquesta fórmula la podem obtenir de representar tots els $N$ equips en una matriu d'adjacència on tindriem $N^2$ cel·les, éssent cada cel·la un enfrentament entre cada equip. Si mirem el nombre de cel·les de la matriu, li treiem aleshores els $N$ elements de la diagonal principal -enfrentament d'un equip amb ell mateix, que no és possible- i després eliminem la meitat dels elements restants de la matriu, és a dir el triangle inferior o superior -ja que no hi ha partits de tornada, i ens quedem amb el triangle restant és quan tenim el nombre d'enfrentaments igual a $N(N-1)/2$:

$$\text{Nombre Enfrentaments} = (N^2 - N)/2 = N(N-1)/2$$

La part del problema més interessant era ordenar la matriu de resultats primer per la columna dels punts (segona columna) i, sense que es perdés aquesta ordenació, ordenar després er la columna de la diferència de gols (cinquena columna) i, finalment, per la columna dels gols a favor (tercera columna). Per fer això es pot aconseguir amb el que anomenen Comparator. No domino la sintaxis i no tinc encara la capacitat tècnica suficient per fer-los servir així que aquesta secció l'he demanat a xatGPT (al programa està indicada la secció que ha fet la IA). Justament és aquesta secció la que genera, pressumptament, un error de compilació (CE) així que la resolució del problema cal interpetar-la amb cautela:


ProblemaE.java:96: error: ')' expected
                    .comparingInt((int[] row) -> row[1]) // Sort by the SECOND column
                                        ^
ProblemaE.java:96: error: illegal start of expression
                    .comparingInt((int[] row) -> row[1]) // Sort by the SECOND column
                                               ^
ProblemaE.java:96: error: ';' expected
                    .comparingInt((int[] row) -> row[1]) // Sort by the SECOND column
                                                       ^
ProblemaE.java:97: error: illegal start of expression
                    .thenComparingInt(row -> row[4])     // Then sort by the FIFTH column
                                           ^
ProblemaE.java:98: error: illegal start of expression
                    .thenComparingInt(row -> row[2])     // Finally, sort by the THIRD column
                                           ^
5 errors

El programa és el següent:

public class ProblemaE {
public static int[][] imprimeixClassificats(int N, int Q, Scanner sc) {
//matriu de la classificació (N equips, 5col)
//columnes: equip, punts, GF, GC, Dif
int[][] m = new int[N][5];

Cada cas de prova del problema F l'he resolt dins la funció tractaCasDeProva() creant un vector per als noms dels equips noms i un altre vector per als gols que marquen els equips gols, cada un d'ells amb N posicions (valor que indica el nombre d'equips que tenim inicialment). Res més començar el processament del cas de prova s'emplenen ambdós vectors amb els N equips (amb N compresa entre 2 i 64, sempre sent potencia de dos).

Després tenim un while amb condició de finalització (N != 1). A la primera iteració del mateix simplement recorrem tot el vector de gols (amb N gols) i mirem els guanyadors dels enfrentaments. Els N/2 valors dels equips guanyadors els reubiquem a la primera meitat de noms i amb la segona meitat del vector ja no hi treballarem.

A la segona iteració enfrentem entre sí els N/2 equips que han guanyat en la fase anterior, guardats com hem dit en la primera meitat del vector noms a la iteració anterior, i entrem pel canal estàndard d'entrada els gols de la següent fase, emmagatzemant-los en la primera meitat del vector gols (de nou, ara treballarem només amb la primera meitat -N/2 valors- del vector gols).

A la tercera iteració o fase mirarem només la primera quarta part del vector noms, ja que tindrem N/4 equips i podrem ara llegir pel canal estàndard d'entrada els gols d'aquesta següent fase, emmagatzemant-los en la quarta primera porció del vector gols... i així successivament fins a trobar-nos que només ens quedin dos equips i l'equip guanyador ens quedi al primer element del vector noms. El bucle pararà quan s'hagi assolit que N/N == 1.

A sota la solució:

public class ProblemaF {
//PRE: N es el nombre d'equips (2 <= N <= 64)
//POST: Mostra pel CE de sortida l'equip guanyador del cas de prova donat.
public static void tractaCasDeProva(int N, Scanner sc) {
//noteu que inicialment nom[i] es l'equip que ha marcat el gols[i]
//en el partit que es celebra entre equps noms[i] i noms[i+1]
String[] noms = new String[N]; //noms dels equips per ordre de sequencia
int[] gols = new int[N]; //gols de l'equip per ordre de sequencia
//GUARDEM ELS NOMS DELS EQUIPS DINS ARRAY DE noms
for (int i = 0; i < N; ++i)
noms[i] = sc.next(); //pillem seguent equip
while (N != 1) {
//LLEGIM ELS N GOLS MARCATS
//(a vuitens de final N gols són 16 nombres, a quarts son 8, ...)
for (int i = 0; i < N; ++i)
gols[i] = sc.nextInt();
//1A ITERACIÓ: NOMÉS RECORRO EL VECTOR DE NOMS FINS A LA MEITAT.
//AMB VECTOR DE GOLS DECIDEIXO ELS EQUIPS GUANYADORS REUBICANT ELS
//NOMS DELS GUANYADORS A LA PRIMERA MEITAT DE ARRAY NOMS.
int j = 0;
int i = 0;
while (j < N - 1) { //també serviria la condicio i < N/2 :)
if (gols[j] > gols[j+1])
noms[i] = noms[j];
else if (gols[j] < gols[j+1])
noms[i] = noms[j+1];
j = j + 2;
i = i + 1;
}
//Passem de la fase de N/2 enfrentaments a N/4
//despres a N/8 fins que N/N = 1 que és quan es troba el guanyador.
N = N/2;
}
sc.nextLine(); //netejo bufer
System.out.println(noms[0]); //mostro guanyador del cas de prova
}
public static void main(String[] args) {
int N;
Scanner sc = new Scanner(System.in);
do {
N = sc.nextInt();
sc.nextLine();
if (N >= 2)
tractaCasDeProva(N, sc);
} while (N != 0);
}
}

3. 2023 (Regional Villaviciosa de Odón i Terrassa)

Aqui hi ha resolució de problemes de regional de villaviciosa d'odón i terrassa 2023, realitzats en la universitat europea i en l'institut nicolau copèrnic.

Dificultat extremadament fàcil. Si es programa de zero es pot tenir el programa llest i entregat en menys de quatre minuts. Nom

La solució en java:

import java.util.Scanner;
/**
* @author santi
* @version 25 mar. 2024
*/
public class picosPatas {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); //n casos
for (int i = 0; i < n; ++i) {
int patosCasProva = sc.nextInt();
System.out.println(patosCasProva+" "+patosCasProva*2);
}
}
}

La solució en C++:

#include <iostream>
using namespace std;
int main() {
int n;
cin >> n; //n casos
int pato;
for (int i = 0; i < n; ++i) {
cin >> pato;
cout << pato << " " << pato*2 << endl;
}
return 0;
}

El problema d'anys de traspàs és típic i no és llarg de fer, però no és trivial de codificar si no es presta atenció als condicionals. L'enunciat no deixava les normes de l'any de traspàs molt molt clares, pel meu gust; així doncs es pot consultar aquest recurs electrònic i després programar-ho a partir de les següents normes:

Para determinar si un año es bisiesto, siga estos pasos:

  • Si el año es uniformemente divisible por 4, vaya al paso 2. De lo contrario, vaya al paso 5.
  • Si el año es uniformemente divisible por 100, vaya al paso 3. De lo contrario, vaya al paso 4.
  • Si el año es uniformemente divisible por 400, vaya al paso 4. De lo contrario, vaya al paso 5.
  • El año es un año bisiesto (tiene 366 días).
  • El año no es un año bisiesto (tiene 365 días).

La solució:

import java.util.Scanner;
/**
* @author santi
* @version 25 mar. 2024
*/
public class diaMundialPiano {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); //n casos
for (int i = 0; i < n; ++i) {
int anyIni = sc.nextInt();
int anyFi = sc.nextInt();
int contaBis = 0;
int contaNoBis = 0;
for (int j = anyIni; j <= anyFi; ++j) {
if (j%4 == 0) {
if (j%100 == 0) {
if (j%400 == 0)
contaBis += 1;
else
contaNoBis += 1;
} else {
contaBis += 1;
}
} else {
contaNoBis += 1;
}
}
System.out.println(contaNoBis+" "+contaBis);
}
}
}

Per resoldre aquest problema he fet servir dos conjunts. No ho diu al problema però se sobreentèn que cal tenir en compte que si un jugador tracta d'endivinar una lletra, i aquesta es repeteix múltiples vegades en la paraula oculta, automàticament haurà adivinat totes les ocurrències de la lletra en la paraula oculta.

Per resoldre'l he fet servir dos instàncies d'una estructura de dades amb les que Java implementa la idea matemàtica de conjunt. Això és important de considerar-ho perquè si no hagués estat així, resoldre el problema amb conjunts seria impossible ja que un conjunt té una i només una ocurrència, no té elements repetits).

Un conjunt e n java s'implementa mitjançant el tipus de dades Set i Hashset en java (cal fer dos imports amb el NetBeans):

S'anomena HashSet i podem implementar-lo d'aquesta forma:

EN GENERAL:
Set<TipusDada> conjuntet = new HashSet<>();

CASOS CONCRETS:
Set<Integer> conjuntEnterets = new HashSet<>();
Set<Character> conjuntCharete = new HashSet<>();
HashSet<String> conjuntStringete = new HashSet<>();

Noteu que fem servir "Integer" en comptes de "int", o "Character" en comptes de "char". Això és perquè demana introduir un tipus de dades "reference type". Els tipus "int" o "char" son tipus primitius i no referencien a res. En canvi, "Integer" o "Character" si que són "Reference types": defineixen classes que "embolcallen" sengles tipus primitius. És important saber-ho perquè si fas Set<int> conjuntEnters = new HashSet<>(); o Set<char> conjuntCaracters = new HashSet<>(); és incorrecte: et donarà error.

El temps dedicat que he invertit en resoldre el problema del verdugo ha estat d'una hora. Després ha calgut debugejar una mica el main per la lectura de la marca final (uns 10 minuts més), perquè calia prémer intro dues vegades per acabar l'execució del programa (donava RTE abans dels canvis i AC després). A continuació la versió que passa el test:

import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
public class Verdugo {
public static void casDeProva(Scanner sc, String pOculta, String proposta) {
Set<Character> setOculta = new HashSet<>(); //lletres de la paraula oculta
Set<Character> setProvades = new HashSet<>(); //lletres ja trobades per l'usuari
//afegim les lletres de la paraula oculta al conjunt
//(aixi duplicats no s'afegeixen)
for (int i = 0; i < pOculta.length(); ++i) {
setOculta.add(pOculta.charAt(i));
}
int fallosOnEtPenjen = 7;
int intentsGastats = 0;
boolean colgando = true;
for (int i = 0; i < proposta.length(); ++i) {
if (!setProvades.contains(proposta.charAt(i))) { //si no s'ha intentat
if (setOculta.contains(proposta.charAt(i))) { //ha encertat lletra
setOculta.remove(proposta.charAt(i));
setProvades.add(proposta.charAt(i));
} else {
intentsGastats += 1;
setProvades.add(proposta.charAt(i));
}
if (setOculta.isEmpty()) {
System.out.println("SALVADO");
colgando = false;
break;
}
else if (intentsGastats == fallosOnEtPenjen) {
System.out.println("AHORCADO");
colgando = false;
break;
}
}
}
if (colgando)
System.out.println("COLGANDO");
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String pOculta;
String proposta;
while (true) {
pOculta = sc.nextLine();
if (pOculta.equals("."))
break;
proposta = sc.nextLine();
// processa el cas de prova
casDeProva(sc, pOculta, proposta);
}
sc.close();
}
}

Per a qualsevol cas de prova donat, veiem que el problema de termoclastismo és senzill de fer per trobar les temperatures màximes i mínimes; però no ho és tant per trobar la diferència mínima entre les posicions que ocupen en la seqüència d'entrada d'una temperatura màxima i una temperatura mínima (és a dir, el que ens defineixen com la distància mínima entre ambdues). La resolució aquí mostrada dóna AC però per a arribar-hi ha calgut passar per diverses Wrong Answers.

import java.util.Scanner;
public class Termoclastismo {
//PRE: n > 0
//POST: llegeix i processa n valors de temperatura "t"
// compresos en interval -50 <= t <= 300
public static void casDeProva(Scanner sc, int n) {
int tMin = 301;
int tMax = -51;
int nMax = 0;
int nMin = 0;
//guardo les tmratures d'un cas d prova per trobar la distancia minima
int[] vTemps = new int[n];
for (int i = 0; i < n; ++i) {
int t = sc.nextInt();
vTemps[i] = t;
//BUSCO T MAXIMES I T MINIMES MENTRE GUARDO LES DADES DE T AL VECTOR
//I BUSCO EL NOMBRE DE TMAXIMES I T MINIMES PER DESPRES PODER CREAR
//ARRAYS QUE CONTINGUIN ELS SEUS INDEXOS (NECESSITEM CALCULAR LA LONGITUD)
//PER EVITAR FER SERVIR ARRAY LIST QUE OCUPA MES MEMORIA
if (t > tMax) {
tMax = t;
nMax = 0;
}
if (t < tMin) {
tMin = t;
nMin = 0;
}
if (t == tMin)
nMin += 1;
if (t == tMax)
nMax += 1;
}
if (tMin == tMax) //SÉ QUE ALESHORES LA DISTANCIA MINIMA SERA 0
System.out.println(tMin+" "+tMax+" "+0);
else { //CAS EN QUE DISTANCIA MINIMA SERA >= 1
System.out.printf(tMin+" "+tMax+" "); //calculo la distancia minima a sota
int[] arr_Imax = new int[nMax];
int[] arr_Imin = new int[nMin];
//GUARDO ELS INDEXOS ON ESTAN LES TEMPERATURES MAXIMES
//I MINIMES REPETIDES RECORRENT EL vTemps:
int posMax = 0;
int posMin = 0;
for (int i = 0; i < vTemps.length; ++i) {
if (vTemps[i] == tMax) {
arr_Imax[posMax] = i;
++posMax;
}
else if (vTemps[i] == tMin) {
arr_Imin[posMin] = i;
++posMin;
}
}
//ARA QUE JA TINC ELS indexos de vTemps on hi ha els (o el) iMax guardats
// dins "arr_Imax" i els (o el) iMin guardats en "arr_Imin" podem veure
// que ambdós arrays estan ordenats ja! Per tant, puc aplicar un algoritme
// de comparació que no requereixi fer comparacions de tots amb tots.
//
// primer començem pel primer valor d'arr_Imin i pel primer de arr_Imax.
// si la diferència en valor absolut és 1 ja hauriem trobat la
// distància mínima i parariem.
// en cas contrari continuariem comparant aumentant l'índex de l'arraylist
// el nombre del qual sigui més petit en la comparació:
//
// vegeu a continuació:
int i = 0;
int j = 0;
int distancia = 200001; //o b 200001
while (i < nMax && j < nMin && distancia != 1) {
//FONAMENTAL FER EL MINIM!
distancia = Math.min(distancia, Math.abs(arr_Imax[i] - arr_Imin[j]));
if (arr_Imax[i] > arr_Imin[j])
j = j + 1;
else if (arr_Imax[i] < arr_Imin[j])
i = i + 1;
}
System.out.println(distancia);
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
while (n != 0) {
casDeProva(sc,n);
n = sc.nextInt();
}
}
}

A continuació explicaré l'estratègia seguida i l'algoritme emprat:

Per trobar tMaxi tMin només cal recórrer la seqüència de temperatures i anar actualitzant sengles variables que haviem creat abans de llegir les dades del cas: així, a cada iteració del recorregut dels valors de la seqüència, si trobem un valor t tal que t < tMin actualitzarem tMin; mentre que si compleix t > tMax aleshores ctualitzerm tMax. Res complicat. Ara bé, el problema de trobar la distància mínima en la seqüència entre la temperatura màxima i mínima no és pas trivial quan tenim temperatures màximes i mínimes repetides. Això, per tant, ens obliga a guardar en un array vTemps totes les temperatures mentre en llegiem la seqüència per trobar iMaxi iMin.

Primer de tot si es dóna que en acabar el recorregut de la seqüència de les temperatures d'entrada es dóna que tMaxi tMin són iguals, aleshores ja sabem que les temperatures trobades en el cas de prova son totes iguals i, per tant, ja haurem trobat que la distancia serà zero i la podrem imprimir per pantalla (no éssent necessari recórrer el vTemps). Anàlogament, si tMaxi tMin no estessin repetits en la seqüència no ens caldria guardar les temperatures en el vector vTemps. Malauradament no és així i el cas més complex és la norma i no l'excpeció: aquest es dóna quan tinguem temperatures màximes i mínimes repetides pero que ni tMax ni tMinsiguin el mateix valor (és a dir, no totes les temperatures de la seqüència siguin iguals), cas en que la única forma que se m'acudeix de resoldre el problema és tornar a recórrer les temperatures, ara ja guardades dins de vTemps, i preguntar quins índexos ocupen els valors de tMax i els valors de tMin. Un cop tinguem la informació, els índexos de les temperatures màximes els guardarem dins de arr_Imaxi els índexos de les temperatures mínimes els guardarem dins de arr_Imin. Per tant, si tenim un cas de prova amb la següent seqüència de temperatures amb 13 valors (1 2 3 3 2 2 2 3 2 1 2 3 1 ) podriem rerpresentar els valors de vTemps, arr_Imax i arr_Imin com si fos Python o pseudocodi de la següent forma:

NOTA: damunt de vTemps escric els indexos del 0 al 9 per legibilitat del vector.

          0|1|2|3|4|5|6|7|8|9| ...
vTemps = [1 2 3 3 2 2 2 3 2 1 2 3 1] 
arr_Imax = [2,3,7,11]
arr_Imin = [0,9,12]

Fixeu-vos que els índexos de les posicions que ocupen les temperatures màximes i mínimes dins d'aquestes llistes estan ordenats dins de cada llista. És fàcil veure a simple vista que la distància mínima la trobarem si comparem la temperatura 3 (temperatura màxima dins vTemps) de la posició 11 i la temperatura 1 (temperatura mínima dins vTemps) de la posició 12. Però per a què ho faci el programa haurem de mirar arr_Imax i arr_Imin: Així doncs per trobar la distància mínima d'índexos de temperatures màximes i mínimes no és necessari que mirem totes les combinacions possibles de les dues arr (molt ineficient) sino comparar element a element i d'esquerra a dreta les dues llistes.

Primer comparem arr_Imax[0] amb arr_Imin[0] (fent abs(arr_Imin[0] - arr_Imax[0])). Si la diferència trobada en valor absolut és 1 aleshores ja hem trobat la distància mínima que buscavem, i no cal seguir mirant les arrays. En cas contrari, caldrà seguir comparant les dues arrays arr_Imax i arr_Imin fins a arribar a una diferència en valor absolut de 1 o bé fins arribar fins al final de les llistes.

Per procedir a cada iteració del bucle incrementarem en 1 l'índex de arr_Imax o arr_Imin segons quin dels dos últims valors d'índex de temperatura que hem comparat entre cada array sigui el més petit. Així doncs, a cada ocasió estarem reduint potencialment la diferència.

Per exemple, a la primera iteració comparem arr_Imax[0] que és 2 amb arr_Imin[0] que és 0 (abs(2-0) --> 2 i per tant no hem acabat). Per tant, a la segona iteració, aumentarem en una unitat l'índex de arr_Imin (que té el valor més petit dels dos que hem comparat). Així doncs a la tercera iteració compararem arr_Imax[0] que és 2 amb arr_Imin[1] que és 9. A la quarta iteració aumentarem l'índex que es mou per la llista arr_Imax (que ara te el valor més petit en la comparació anterior) i per tant compararem arr_Imax[1] que és 3 amb arr_Imin[1] que és 9... I aixi fins arribar a trobar la diferència mínima que en aquest cas de prova es trobarà just abans del final de les llistes, quan fem la comparació arr_Imax[3] que és 11 i arr_Imin[2] que és 12.

Aquest algoritme per cercar la distància mínima queda exemplificat en aquest gif per al cas particular que esmentavem (compte, l'algoritme en el gif no recull un error que havia comès inicial: que cal fer distancia = Math.min(distancia, Math.abs(arr_Imax[i] - arr_Imin[j])); en comptes de distancia = Math.abs(arr_Imax[i] - arr_Imin[j])):

El gif no ha carregat

I el fragment de codi:

int i = 0;
int j = 0;
int distancia = 200001; //o b 200001
while (i < nMax && j < nMin && distancia != 1) {
//FONAMENTAL FER EL MINIM!
distancia = Math.min(distancia, Math.abs(arr_Imax[i] - arr_Imin[j]));
if (arr_Imax[i] > arr_Imin[j])
j = j + 1;
else if (arr_Imax[i] < arr_Imin[j])
i = i + 1;
}
System.out.println(distancia);
}

4. 2023 (Regional de Zaragoza)

Aquí poso les meves solucions a problemes de la competició regional de saragossa al centre San Valero, l'any 2023:

Existeixen quatre casos a tenir en compte en aquest problema per quan recorrem els elements de la espiral: quan ens movem cap amunt (disminuim index de fila una unitat), cap a la dreta (aumentem índex de columna una unitat), cap abaix (aumentem index de fila una unitat) i cap a l'esquerra (disminuim index de columna una unitat). Per a cada un dels casos cal prestar atenció a les condicions de finalització dels bucles.

Per a fer-lo i que sigui acceptat amb veredicte AC a acepta el reto he tardat una hora. Cal vigilar on incrementem variables com "nreEstrellesEnLinia" i ser curós: ja que fem servir quatre índexos per resoldre'l. És fàcil confondre's i incrementar dins un for un index que no és:

import java.util.Scanner;
public class EspiralGalactica {
//PRE: N imparell i 0 <= N <= 100 000
public static void casDeProva(Scanner sc, int N) {
int[][] m = new int[N][N];
//GUARDO MATRIU DEL FIRMAMENT
for (int i = 0; i < N; ++i) {
for (int j = 0; j < N; ++j) {
m[i][j] = sc.nextInt();
}
}
//CONTO ESTRELLES EN L'ESPIRAL (POSICIO INICIAL ESPIRAL EN i I j N mig)
int i = N/2;
int j = N/2;
int sumE = m[i][j]; //inicialitzo amb el valor de la casella central
boolean espiralSumada = false;
String[] cas = {"amunt","dreta","avall","esquerra"};
int nreEstrellesEnLinia = 2; //primer 2 celes, despres 3, despres 4...
while (!espiralSumada) {
for (int k = 0; k < cas.length; ++k) {
for (int l = 0; l < nreEstrellesEnLinia - 1; ++l) {
if (cas[k].equals("amunt")) {
i = i - 1;
if (i >= 0)
sumE += m[i][j];
else {
espiralSumada = true;
break;
}
} else if (cas[k].equals("dreta")) {
j = j + 1;
if (j < N)
sumE += m[i][j];
else {
espiralSumada = true;
break;
}
} else if (cas[k].equals("avall")) {
i = i + 1;
if (i < N)
sumE += m[i][j];
else {
espiralSumada = true;
break;
}
} else if (cas[k].equals("esquerra")) {
j = j - 1;
if (j >= 0)
sumE += m[i][j];
else {
espiralSumada = true;
break;
}
}
}
if (espiralSumada)
break;
nreEstrellesEnLinia += 1;
}
}
System.out.println(sumE);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
while (N != 0) {
casDeProva(sc,N);
N = sc.nextInt();
}
sc.close();
}
}

Per resoldre aquest problema i obtenir AC he tardat 30 minuts (no s'han necessitat diversos enviaments). Calia fer ús del mètode sc.nextLine() per obtenir cada linia (cas de prova) i funcions com linia.Split(" ") per tallar el contingut de cada linia en paraules. Un cop fet això podiem trobar si una paraula començava per majusucula o no amb Character.isUpperCase (fent servir el mètode dels strings charAt() passant el valor 0 com a paràmetre per obtenir la primera lletra de cada paraula):

import java.util.Scanner;
public class Acronimos {
//PRE: n casos de prova (linies que tenen paraules)
//POST: sortida per pantalla per a cada cas de prova [DEFINIR]
public static void casDeProva(int n, Scanner sc) {
String linia = sc.nextLine();
String[] par = linia.split(" ");
for (int i = 0; i < par.length; ++i) {
String sortida = "";
if (Character.isUpperCase(par[i].charAt(0))) {
System.out.print(par[i].charAt(0));
}
}
System.out.println("");
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
//llegeixo l enter que informa del nre de casos de prova totals (nCasos)
int nCasos = sc.nextInt();
sc.nextLine();
//menjo i tracto cada cas de prova amb la funciO corresponent
for (int i = 0; i < nCasos; ++i) {
casDeProva(nCasos, sc);
}
sc.close();
}
}

Per resoldre aquest problema he tardat 23 minuts. He optat per passar cada cas de prova (un enter) a un string. Aquest string l'he passat a un vector d'enters vN_petit, l'he ordenat amb el mètode sort per generar el valor més petit. Després he fet servir aquest vector ordenat per generar el valor més gran (en un altre vector d'enters vN_gran simplement invertint l'ordenació del vector anterior). Aquests vectors d'enters els he fet servir per crear sengles strings que tenen els valors dels enters passats a string i concatenats. Després he passat de nou a enter, prèviament eliminant el padding de zeros a l'inici de l'string dels valors petits amb petit = petit.replaceFirst("0+", "");. En aquesta funció el primer paràmetre es una expressió regular ("0+" significa esborrar totes les ocurrencies de zero -+ significa totes-) però com que fem servir la funció replaceFirst en comptes de replaceAll, el que farà serà eliminar només les ocurrències de zeros a l'esquerra dels petits (no a tot l'string).

import java.util.Arrays;
import java.util.Scanner;
public class CuriosaPropiedad9 {
public static void casDeProva(Scanner sc, int n) {
String nS = Integer.toString(n);
int[] vN_petit = new int[nS.length()];
int[] vN_gran = new int[nS.length()];
for (int i = 0; i < nS.length(); ++i) {
vN_petit[i] = Integer.parseInt(""+nS.charAt(i));
}
Arrays.sort(vN_petit);
for (int i = 0; i < nS.length(); ++i) {
vN_gran[i] = vN_petit[nS.length()-1-i];
}
String petit = "";
String gran = "";
for (int i = 0; i < nS.length(); ++i) {
petit += Integer.parseInt(""+vN_petit[i]);
gran += Integer.parseInt(""+vN_gran[i]);
}
petit = petit.replaceFirst("0+", ""); //uso expressio regularexpression er treure el padding de l'esquerra de zeros
int diferencia = Integer.parseInt(gran) - Integer.parseInt(petit);
int quocient = diferencia/9;
System.out.println(gran+" - "+petit+" = "+diferencia+" = "+quocient+" x "+9);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
while (n != 0) {
casDeProva(sc,n);
n = sc.nextInt();
}
sc.close();
}

5. 2024 (Regional de València)

Aquests problemes pertanyen a la programaMe regional de València organitzada per IES Serra Perenxisa (Torrent, València). En aquesta competició hi vaig participar presencialment amb un company en una aula de l'IES Abastos (els problemes dels apartats previs conformaven la preparació per aquesta competició). Vam aconseguir enviar correctament els problemes A, B i D (en equip de 2, el tercer integrant va faltar) dins les dues hores que teniem per a pujar-ho al jutge online de la competició.

Per accedir als enunciats dels problemes podeu veure el PDF següent: link

TO DO

Aquest és el problema tal qual es va enviar a la competició, amb veredicte AC. Per resoldre'l cal verificar que:

  1. El primer digit de cada cas de prova sigui igual al penúltim dígit del mateix i que el segon digit de cada cas de prova sigui igual a l'últim del mateix. Si no es dóna això, ja podem imprimir "ERROR" i anar a processar el següent cas de prova.
  2. Si es dóna l'anterior premisa, aleshores hem de mirar que el nombre d'uns i de zeros de la següència o cas de prova sigui el mateix i imprimir que la sequència és "EQUILIBRADA".

Cada cas de prova ocupa una línia i es llegeix pel CEE amb sc.nextLine(), accendint després a cada caràcter (cada 1 o cada 0) amb la funció dels strings charAt(). Aquest és el codi del problema:

import java.util.Scanner;
public class ProblemaB {
public static boolean casDeProva(Scanner sc) {
String nS = sc.nextLine();
int llarg = nS.length();
boolean iniciFiOK = false;
if (nS.charAt(0) == nS.charAt(llarg-2)) {
if (nS.charAt(1) == nS.charAt(llarg-1)) {
iniciFiOK = true;
}
}
if (iniciFiOK) {
int nZeros = 0;
int nUns = 0;
for (int i = 0; i < llarg; ++i) {
if (nS.charAt(i) == '0')
nZeros += 1;
else
nUns += 1;
}
if (nZeros == nUns)
return true;
else
return false;
} else {
return false;
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
//llegeixo l enter que informa del nre de casos de prova totals (nCasos)
int nCasos = sc.nextInt();
sc.nextLine();
//menjo i tracto cada cas de prova amb la funciO corresponent
for (int i = 0; i < nCasos; ++i) {
if (casDeProva(sc)) {
System.out.println("EQUILIBRADA");
} else {
System.out.println("ERROR");
}
}
sc.close();
}
}

Aquest problema el vaig intentar a la competició programaMe però no em va donar temps d'acabar-lo. Aquesta versió, modificada a posteriori, passa els casos de prova especificats a sota però NO ha passat ENCARA el jutge online (queda per verificar-lo un cop pengin les solucions al jutge online, així que prego al lector llegir el codi amb cautela).

La lectura de l'entrada té un nombre T que informa del nombre de casos de prova. Després trobem, per a cada cas de prova, dues línies que el composen. A continuació mostrem l'exemple d'input públic que van mostrar durant la competició:

3
4 10 5 5 10
1 1 1 1
4 10 5 5 10
1 2 3 1
7 0 5 0 2
3 1 1 2 3 3 3

Els outputs que el programa ha de generar pels tres casos de prova anteriors són:

19
18
5

En la primera línia d'un cas de prova qualsevol trobem els nombres (per ordre) N, A, B, X i Y. Éssent N el nombre d'usuaris, A el nombre de likes que té el servidor A inicialment, B el nombre de dislikes que té el servidor A inicialment, X el nombre de likes que té el servidor B inicialment i, finalment, Y el nombre de dislikes que té el servidor B inicialment.

El problema en essència ens diu que, a partir d'aquestes dades, tenim dos servidors on es poden fer votacions de like i dislike, cada un amb un contador independent de likes i dislikes. Després, també ens diu que tenim tres tipus d'usuaris: el tipus 1, que dóna sempre like en una votació; El tipus 2, que dóna sempre dislike en una votació; i el tipus 3, que és un usuari influenciable pel feedback que rep del servidor on l'enviem a votar: dóna dislike sempre que vegi que en el servidor hi hagi registrats més dislikes que likes o, en cas contrari (si veu que hi ha més likes que dislikes o el mateix nombre de likes que de dislikes) aleshores dóna un like.

Finalment, en el problema se'ns demana que maximitzem la suma dels likes dels dos servidors. Aquesta maximització l'haurem de fer manipulant a quin servidor voldrem enviar cada usuari, perquè voti segons ens convingui a nosaltres. Considerant que cada usuari vota una i només una vegada en un sol servidor, i que no pot escollir no votar, i deixant de banda l'ètica implicada en aquest enunciat, aquí està el codi que resol el problema tal i com jo l'he entès (a sota del codi explico l'estratègia per solucionar el problema i així entendre'l millor):

import java.util.Scanner;
public class ProblemaC {
//PRE: la sequencia de N A B X Y en una linia seguida de N enters en la seguent.
//POST: sortim el nombre de likes combinat dels dos servidors per cada cas de prova.
public static void casDeProva(Scanner sc) {
int N = sc.nextInt();
int A = sc.nextInt(); //gustas server A
int B = sc.nextInt(); //no gustas server A
int X = sc.nextInt(); //gustas server B
int Y = sc.nextInt(); //no gustas server B
//usuaris de tipus 1 (el que dona like si li agrada)
//usuaris de tipus 2 (el que dona dislike si no li agrada)
//usuaris de tipus 3 son els que donen like o dislike en base al
// feedback del server (si el server te mes likes que dislikes o igual
//nombre de likes o dislikes l'usuari votarà un like, mentre que
//en cas contrari (mes dislikes que likes) votarà un dislike.
for (int i = 0; i < N; ++i) {
int tipusLlegit = sc.nextInt();
int dA = A - B;//diff de vots positius - negatius en servidor A
int dB = X - Y;//diff de vots positius - negatius en servidor B
//El que sempre vota positiu (tipus1) acumula el seu vot positiu al
//servidor amb mes vots positius que negatius si es el cas.
if (tipusLlegit == 1) {
if (dA >= dB) //en cas dA == dB fer X += 1 o A += 1 es indistint
A += 1;
else if (dA < dB)
X += 1;
}
//En canvi, el que sempre vota negatiu (tipus 2) acumula el seu vot
//negatiu al servidor amb mes vots negatius que positius si es l cas
else if (tipusLlegit == 2) {
if (dA >= dB) //en cas dA == dB --> B += 1 o Y += 1 es indistint
Y += 1; //sumo als dislikes del servid. B (el pitjor serv.)
else if (dA < dB)
B += 1; //sumo als negatius del servidor A
}
//Finalment, els que voten a partir del feedback que reben del
//servidor caldrà que els dirigim sempre en el servidor on hi
//hagi una diferència més favorable. ELLS SON ELS QUE MARQUEN
//la diferència per poder manipular els vots.
else if (tipusLlegit == 3){
if (dA > dB) { //serv. A aquí t resultats més favorables que B
if (A >= B) //USUARI3 donarà vot positiu! El posem al millor server (A).
A += 1;
else //SI (A < B) faria dislike al serverA. Solució: el posem al server B i preservem server A.
Y += 1;
}
else if (dA <= dB) { //QUAN (dA < dB SERVIDOR B TE RESULTATS MES FAVORABLES (SI dA == dB es igual a quin servidor sumem els vots)).
if (X >= Y) //si (X>Y) USUARI 3 DONA VOT POSITIU AL SERVIDOR B. EL POSEM AL B.
X += 1;
else //SI (X < Y) USUARI 3 ANIRIA A DONAR UN VOT NEGATIU AL SERVIDOR B. PER TANT ELVOT NEGATIU EL POSEM AL server A.
B += 1;
}
}
}
//IMPRIMEIXO SUMATORI DE VOTS POSITIUS DELS DOS SERVIDORS
System.out.println(A+X);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int T = sc.nextInt(); //casos de prova totals
for (int i = 0; i < T; ++i) {
casDeProva(sc);
}
sc.close();
}
}

L'estratègia que he emprat per solucionar el problema, a grans trets, és la següent:

  1. Els usuaris tipus 1 està clar que caldrà enviar-los al servidor que tingui més likes en relació al nombre de likes (al "millor" servidor).
  2. Als usuaris de tipus 2 caldrà enviar-los al servidor que tingui un nombre menor de likes en relació al nombre de dislikes (és a a dir al "pitjor" servidor).
  3. Els usuaris de tipus 3 ja són més complexos. En essència només els enviarem al "millor" servidor si en podem aconseguir incrementar els likes i, si veiem que no aconseguim que l'usuari n'aumenti els likes, per evitar que ens posi un dislike al millor servidor, l'enviarem a votar al "pitjor" perquè el compteig negatiu no afecti al "millor":

Per trobar quin és el millor servidor tenim les variables dA i dB, que les calculem fent i les descrivim en els comentaris:

int dA = A - B;//diff de vots positius - negatius en servidor A
int dB = X - Y;//diff de vots positius - negatius en servidor B

Per exemple, anem a posar un cas particular: si fem una comparació com la seqüent dA > dB i aquesta és certa, això significa que el servidor A té mes likes en relació a dislikes que el servidor B (és el B el que, en aquest cas particular, anomeno "millor" servidor). En aquest cas també evitarem tot allò que ens pugui penalitzar amb un vot negatiu en el "millor" servidor, és a dir, que un usuari de tipus 3 voti en A si el servidor A té més dislikes que likes o que un usuari de tipus 2 voti en el servidor A; pel contrari, sota aquest supòsit, farem tot allò que ens doni un vot al "millor" servidor: és a dir, que l'usuari de tipus 3 voti en A si el servidor A té més likes que dislikes (o igual nombre de likes que de dislikes) o que l'usuari 1 també hi voti.

A cada introducció d'un usuari el programa reevaluarà els valors dA i dB per prendre en cada cas la decisió més acertada.

En essència la porció del codi que s'executarà per a cada usuari que llegim (1, 2 o 3 -del tipus corresponent-) s'executarà aquest codi:

int tipusLlegit = sc.nextInt();
int dA = A - B;//diff de vots positius - negatius en servidor A
int dB = X - Y;//diff de vots positius - negatius en servidor B
//El que sempre vota positiu (tipus1) acumula el seu vot positiu al
//servidor amb mes vots positius que negatius si es el cas.
if (tipusLlegit == 1) {
if (dA >= dB) //en cas dA == dB fer X += 1 o A += 1 es indistint
A += 1;
else if (dA < dB)
X += 1;
}
//En canvi, el que sempre vota negatiu (tipus 2) acumula el seu vot
//negatiu al servidor amb mes vots negatius que positius si es l cas
else if (tipusLlegit == 2) {
if (dA >= dB) //en cas dA == dB --> B += 1 o Y += 1 es indistint
Y += 1; //sumo als dislikes del servid. B (el pitjor serv.)
else if (dA < dB)
B += 1; //sumo als negatius del servidor A
}
//Finalment, els que voten a partir del feedback que reben del
//servidor caldrà que els dirigim sempre en el servidor on hi
//hagi una diferència més favorable. ELLS SON ELS QUE MARQUEN
//la diferència per poder manipular els vots.
else if (tipusLlegit == 3){
if (dA > dB) { //serv. A aquí t resultats més favorables que B
if (A >= B) //USUARI3 donarà vot positiu! El posem al millor server (A).
A += 1;
else //SI (A < B) faria dislike al serverA. Solució: el posem al server B i preservem server A.
Y += 1;
}
else if (dA <= dB) { //QUAN (dA < dB SERVIDOR B TE RESULTATS MES FAVORABLES (SI dA == dB es igual a quin servidor sumem els vots)).
if (X >= Y) //si (X>Y) USUARI 3 DONA VOT POSITIU AL SERVIDOR B. EL POSEM AL B.
X += 1;
else //SI (X < Y) USUARI 3 ANIRIA A DONAR UN VOT NEGATIU AL SERVIDOR B. PER TANT ELVOT NEGATIU EL POSEM AL server A.
B += 1;
}
}

En acabar de llegir l'últim usuari, imprimirem el nombre de likes total del servidor fent:

Aquest és el problema tal qual es va enviar a la competició, amb veredicte AC. Pot ser que no sigui el més elegant però és tal qual se va enviar i volia conservar-ho com a tal. Cal tenir en compte que, per fer aquest problema, hem d'anar mirant, per a cada tauler (o cas de prova), tant 1 com 2:

  1. Si algú ha tirat al centre (O o X)
  2. Si el que tira al centre es O, aquest jugador potencialment podrà arribar a tenir 5 tirades mentre que l'altre només 4 (ja que els jugadors tiren de forma alterna i el tauler té un nombre senar de caselles); i viceversa, si el que tira primer al centre es X això voldrà dir que X tindrà una tirada més que O. També es pot donar que un cas de prova o tauler tingui el mateix nombre de tirades tant d'un com de l'altre (per exemple, perquè sigui una partida inacabada en que senzillament queda un torn per tirar en algun dels dos jugadors). Així doncs, a més a més de comprovar que un jugador (sigui O o X) hagi tirat al centre de la casella per descartar que la partida sigui "IMPOSIBLE" també cal que comprovar el seguent: si O ha tirat primer caldrà que es compleixi que nO - nX == 1 o bé que nO - nX == 0. Anàlogament, si X ha tirat primer caldrà que es compleixi que nX - nO == 1 o bé que nX - nO == 0.

Per llegir cada cas de prova (cada tauler de tres en ratlla) em fet servir tres cops la funció sc.nextLine().

Aquest és el codi del problema:

import java.util.Scanner;
public class ProblemaD {
//PRE: n casos de prova (es possible que es pugui esborrar)
//POST: sortida per pantalla per a cada cas de prova [DEFINIR]
public static void casDeProva(Scanner sc) {
int nO = 0;
int nX = 0;
String judici = "";
String linia = sc.nextLine();
String jugada;
//consumim primera linia
for (int i = 0; i < 3; ++i) {
jugada = ""+linia.charAt(i);
if (jugada.equals("O"))
nO += 1;
else if (jugada.equals("X"))
nX += 1;
}
//consumim segona linia
linia = sc.nextLine();
for (int j = 0; j < 3; ++j) {
jugada = ""+linia.charAt(j);
if (jugada.equals("O"))
nO += 1;
else if (jugada.equals("X"))
nX += 1;
if (j == 1) {
if (jugada.equals("-"))
judici = "IMPOSIBLE";
else if (jugada.equals("O"))
judici = "CIRCULO";
else
judici = "CRUZ";
}
}
//consumim tercera linia
linia = sc.nextLine();
for (int i = 0; i < 3; ++i) {
jugada = ""+linia.charAt(i);
if (jugada.equals("O"))
nO += 1;
else if (jugada.equals("X"))
nX += 1;
}
//JUDICI
if (judici.equals("IMPOSIBLE"))
System.out.println("IMPOSIBLE");
else if (judici.equals("CIRCULO")) {
if (nO - nX == 1 || nO - nX == 0)
System.out.println("CIRCULO");
else
System.out.println("IMPOSIBLE");
}
else {//caso cruz {
if (nX - nO == 1 || nX - nO == 0)
System.out.println("CRUZ");
else
System.out.println("IMPOSIBLE");
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
//llegeixo l enter que informa del nre de casos de prova totals (nCasos)
int nCasos = sc.nextInt();
sc.nextLine();
//menjo i tracto cada cas de prova amb la funciO corresponent
for (int i = 0; i < nCasos; ++i) {
casDeProva(sc);
}
sc.close();
}
}

TO DO

TO DO

NOTA: Codi del problema G no testejat amb jutge online.

Per tenir eficiència computacional en aquest problema necessitem una estructura de dades que ens guardi les habilitats d'un grup de desenvolupadors, eliminant-ne les habilitats repetides (només necessitem saber les habilitats que hi ha en el grup, no quants ni quins desenvolupadors les tenen). També necessitem una estructura de dades que pugui cercar de forma eficient cada una de les habilitats dels desenvolupadors guardades, a mesura que anem llegint pel canal estàndard d'entrada les habilitats que el grup de desenvolupadors necessita per resoldre cada un dels projectes que van entrant. Tot això ens ens ho permet fer un hashSet. Les habilitats dels desenvolupadors les guardarem dins d'un set de Strings al que anomenarem tecDes.

Aquest són els casos de prova públics (en l'exemple següent tenim un grup amb 5 desenvolupadors -en majúscules les habilitats, els noms no són rellevants-):

5
Alice FRONTEND BACKEND
Bob DATABASE SECURITY
Charlie FULLSTACK
Diana FULLSTACK DATABASE
Eva SECURITY
3
SistemaGestion FRONTEND BACKEND DATABASE
WebSegura SECURITY DATABASE
PlataformaEducativa FULLSTACK UI/UX

I la sortida corresponent als mateixos ha de ser:

POSIBLE
POSIBLE
IMPOSIBLE

El codi realitzat que resolt el problema de forma eficient és:

import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
public class ProblemaG {
public static Set<String> llegeixDesenvolupadors(Scanner sc) {
//el conjunt de tecnologies que tenen els desenvolupadors
Set<String> tecDes = new HashSet<>();
int D = sc.nextInt(); //llegeixo el nombre D de desenvolupadors
sc.nextLine(); //netejo bufer després de llegir enter i abans del proper nextline
//recorrem cada linia (cada linia es un desenvolupador
//seguit d ls seves habilitats)
for (int i = 0; i < D; ++i) {
String linia = sc.nextLine();
String[] hd = linia.split(" "); //hd es habiliatt desenvolupador
//afegim les habilitats del desenvolupador al set
//(ens despreocupem dels duplicats, el set ja se n'encarrega que no
//n'hi hagin)
for (int j = 1; j < hd.length; ++j)
tecDes.add(hd[j]);
}
return tecDes;
}
public static void llegeixProjectes(Scanner sc, Set<String> tecDes) {
int P = sc.nextInt(); //llegeixo el nombre P de projectes
sc.nextLine();
//RECORRO ELS PROJECTES
for (int i = 0; i < P; ++i) {
String linia = sc.nextLine();
String[] hrp = linia.split(" "); //hrp es habilitats requerida pel projecte
//RECORRO LES HABILITATS REQUEREIDES PER CADA PROJECTE I SI ALGUN
//PROJECTE VEIEM QUE ELS SEUS DESENVOLUPADORS FALLEN EN ALGUNA HAB
//ILITAT PAREM I SORTIM DEL BUCLE SEGUENT:
boolean projectePossible = true;
for (int j = 1; j < hrp.length; ++j) {
if(!(tecDes.contains(hrp[j]))) {
System.out.println("IMPOSIBLE");
projectePossible = false;
break;
}
}
if (projectePossible)
System.out.println("POSIBLE");
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
llegeixProjectes(sc, llegeixDesenvolupadors(sc));
}
}

TO DO

Altres problemes de preparació

És un problema per practicar arrays bidimensionals (podeu veure l'enunciat clicant al títol o aquí: en pdf), lectura de seqüències de nombres enters amb marca final coneguda (0) i, al menys així l'he fet jo, es pot utilitzar l'estructura de dades hashSet per resoldre trobar una de les condicions per decidir si un quadrat (matriu) és, a més a més de diabòlic, esotèric.

La solució:

import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
/** @author santi
* @version 21 mar. 2024 */
public class cuadratDiabolic {
public static int[][] llegirQuadrat(int n, Scanner sc) {
int[][] m = new int[n][n];
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
m[i][j] = sc.nextInt();
}
}
return m;
}
public static boolean esQuadratDiabolic(int[][] m) {
int n = m.length;
//MIRO A FILES (finestra desplaSSSable)
int sumImp = 0; //fila imparell (si no inicialitzo a 0 dona error. PQ?)
int sumPar = 0; //fila parell (si no inicialitzo a 0 dona error. PQ?)
for (int i = 0; i < n - 1; ++i) {
sumImp = 0;
sumPar = 0;
for (int j = 0; j < n; ++j) {
sumImp += m[i][j];
sumPar += m[i+1][j];
}
if (sumPar != sumImp)
return false;
}
// SI HEM ARRIBAT FINS AQUI EL PARELL DE FILES m[i] i m[i+1]
// SUMARa IGUAL (sumPar == sumImp). ALESHORES ENS OCUPEM DE MIRAR
// LA RESTA DE REQUISITS D'UN QUADRAT ESOTeRIC.
// AR ANEM A MIRAR SI ES COMPLEIX PER COLUMNES I PER LES DUES DIAGONALS
// PRINCIPALS.
//COMPROVO COLUMNES : sumCol haura de ser igual a sumPar o sumImp.
for (int i = 0; i < n; ++i) {
int sumCol = 0;
for (int j = 0; j < n; ++j) {
sumCol += m[j][i];
}
if (sumCol != sumPar)
return false;
}
//COMPROVO DIAGONALS PRINCIPAL I DIAGONAL SECUNDARIA
int sumDiagSecundaria = 0;
int sumDiagPrincipal = 0;
for (int i = 0; i < n; ++i) {
sumDiagPrincipal += m[i][i];
sumDiagSecundaria += m[i][n-1-i];
}
//com s ha dit puc comparar sumDiagSecundaria amb sumCol
//o sumPar (ja que en aquest punt del programa tenen el mateix valor).
return sumDiagSecundaria == sumPar;
}
public static boolean esEsoteric(int[][] m) {
int n = m.length;
int rangNombres = n*n;
//trobem la constant mAgica (CM) i CM2:
int CM = 0;
for (int nre: m[0])
CM += nre;
int CM2 = 4*CM/n;
//AVALUEM CONDICIONS
//(DE MeS SENZILLA A MeS COSTOSA COMPUTACIONALMENT)
//ESCAIRES O BORDES (INDISTINT SI n parell o imparell):
int sumaBordes = m[0][0] + m[n-1][0] + m[0][n-1] + m[n-1][n-1];
if (sumaBordes != CM2)
return false;
if (n%2 != 0) { //n imparell (CONDICIo 2 i 3)
//CENTRE
int centreX4 = m[n/2][n/2] * 4;
if (centreX4 != CM2) {
return false;
}
//CENTRES LATERALS
int sumaMigLtrls = m[0][n/2] + m[n/2][0] + m[n-1][n/2] + m[n/2][n-1];
if (sumaMigLtrls != CM2) {
return false;
}
} else { //n parell (CONDICIO 2 i 4)
//SUMA CENTRES (GROC)
int cDaltEsq = m[(n/2)-1][(n/2)-1];
int cDaltDre = m[(n/2)-1][(n/2)];
int cBaixEsq = m[(n/2)][(n/2)-1];
int cBaixDre = m[n/2][n/2];
if (cDaltEsq + cDaltDre + cBaixEsq + cBaixDre != CM2){
return false;
}
//SUMA MIG LATERALS (VERD)
int dalt = m[0][(n/2)-1] + m[0][n/2];
int baix = m[n-1][(n/2)-1] + m[n-1][n/2];
int esq = m[(n/2)-1][0] + m[n/2][0];
int dre = m[(n/2)-1][n-1] + m[n/2][n-1];
if (dalt + baix + esq + dre != 2*CM2) {
return false;
}
}
//SI TOT ES COMPLEIX COMPROVEM CONDICIo 1 (MeS COSTOSA, AL FINAL)
Set<Integer> conjunt = new HashSet<>();
for (int i = 1; i <= rangNombres; ++i)
conjunt.add(i);
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if(conjunt.remove(m[i][j])) {
--rangNombres;
}
}
}
if (rangNombres != 0)
return false;
return true;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = -1;
int[][] m;
while (n != 0) {
n = sc.nextInt(); //dimesiO del quadrat
if (n != 0) {
m = llegirQuadrat(n,sc);
boolean diabolic = esQuadratDiabolic(m);
if (diabolic)
if (esEsoteric(m))
System.out.println("ESOTERICO");
else
System.out.println("DIABOLICO");
else
System.out.println("NO");
}
}
}
}

Footnotes

  1. Per exemple, en java ens cal instanciar un objecte de la classe Scanner Scanner sc = new Scanner(System.in) i ens cal cridar mètodes dinàmics de la mateixa int varEntrada = sc.nextInt() per introduir dades pel canal estàndard d'entrada; en canvi, en C++ només ens cal fer #include <iostream> i cin >> varEntrada després de declarar varEntrada com a entera. També es simplifica el canal estàndard de sortida: en comptes de System.out.println(valor) fem cout << valor << endl. Finalment, en C++ tampoc ens cal escriure el codi d'una classe ni la fila llarguíssima del main que requereix java public static void main(String[] args), que es redueix a int main() { //codi aqui return 0;}.

About

Aquest repositori conté problemes que he resolt en la preparació per a la ProgramaMe. També pot servir com a manual de preparació per a altres participants. Conté una resolució detallada de diversos exercicis de competicions regionals, a més d'una valoració subjectiva del grau de dificultat, per ajudar als estudiants a triar els que volen practicar

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published