There and back again…

Eu até tentei escrever um artigo por dia na semana passada, durante o curso da seletiva brasileira para a Olimpíada Internacional de Informática, mas não deu tempo… Então aqui vai um resumo feito uma semana depois do final do curso, separado em tópicos, com o título pouco criativo “There and back again…”

Nível mais alto

Comparando a prova da segunda fase da OBI2006 com a prova da OBI do ano passado, já era possível perceber que o nível esse ano subiu. E, como já esperado, o nível do curso e das pessoas também subiu, o que é excelente para o Brasil.

Mais prática, menos teoria

Neste ano não aconteceu a programação “normal” que tivemos nos últimos dois anos: aula teórica (só um professor apresentando slides e nós anotando, sem computadores) durante a manhã e aula prática durante a tarde.

Nos dois períodos nossas aulas foram no laboratório, com computadores, onde resolvemos uma porção de problemas do treinamento para a ACM da Universidade de Valladollid.

Criei uma pasta no meu servidor com todos os problemas que eu consegui resolver (alguns deles ficaram pela metade): tiagomadeira.net/pub/uva/ (essa pasta infelizmente foi perdida com o tempo).

Problemas sobre mais de um conteúdo

Uma característica interessante dos problemas desse ano (do treinamento e da prova) foi o uso de mais de um tipo de algoritmo para fazer a solução. A combinação mais comum foi geometria + grafos, que caiu inclusive na prova da Seletiva, no problema Labirinto.

A Prova

Primeiro Dia – Quadrado Romano

[Enunciado] 30 minutos tentando pensar em algum tipo de recorrência, 1h implementando uma solução força bruta! No final, pensei que faria uns 50 pontos (perderia um pouco por causa do tempo), mas perdi alguns pontos por resposta errada, ainda não sei por quê!

Solução: romano.c (infelizmente foi perdido com o tempo)

Pontos: 10/100

Segundo Dia – Euros

[Enunciado] Durante as duas horas da prova fiquei procurando uma recorrência. Descobri que estou muito newbie em programação dinâmica. Não programei uma linha de código…

Pontos: 0/100

Terceiro Dia – Labirinto

[Enunciado] Olhei o enunciado e já saquei o que o problema queria. Eu tenho uma boa noção de grafos (embora precise estudar fluxos) e o curso trabalhou bastante algoritmos geométricos, então sem maiores problemas fiz o algoritmo, testei várias vezes, achei que ia tirar 100. No fim, não sei o que houve, se foi falta de tempo ou resposta errada. Só vi que fiz 40 pontos…

Solução: labirinto.c (infelizmente foi perdido com o tempo)

Pontos: 40/100

Quarto Dia – Prova Final

[Caderno de Tarefas] Li todos os problemas e achei que poderia ir bem na prova (o primeiro problema eu tinha certeza que não conseguiria, mas o segundo e o terceiro dava pra fazer). Então, fui direto para o segundo problema, acreditando que era o mais fácil. Mas depois de bolar vários algoritmos, ter respostas erradas e tempos muito altos, tive que ficar com uma solução precária, um Floyd Warshall para cada troca de vértices (resultado: [tex]O(n^{5})[/tex]!) Aí depois não deu tempo de fazer o terceiro problema, mesmo eu tendo esboçado sua solução.

Solução do Teletransporte: tele.c (infelizmente foi perdido com o tempo)

Pontos: 40/300

Conclusão

O legal é que esse curso sempre dá vontade de estudar, além de ensinar bastante… Aqui ficam registrados meus objetivos e metas pro segundo semestre de 2006 e primeiro semestre de 2007.

Objetivos

Metas

  • Comprar e ler Programming Challenges.
  • Estudar programação dinâmica. Conhecer os algoritmos mais comuns.
  • Estudar fluxos em rede e ordenação topológica.
  • Estudar matemática, inclusive recorrências e geometria (que não ajudam só para olimpíada de matemática, mas pras olimpíadas de informática também)

Anunciada a OBI2006

Anunciada a OBI2006! E temos novidades…

A partir desse ano a OBI será realizada em duas fases: a primeira fase nas escolas cadastradas e a segunda, com os melhores classificados da primeira fase, em universidades localizadas nas capitais dos estados e em cidades com grande concentração de competidores.

As provas da primeira fase da OBI2006 serão realizadas no dia 08 de abril (sábado) para a modalidade Programação: a prova do nível 1 ocorrerá no período da manhã, e a prova do nível 2 ocorrerá no período da tarde. Na modalidade Iniciação a prova será aplicada em dia a ser definido pela escola, entre 06 e 13 de abril.

As provas da segunda fase serão realizadas no dia 13 de maio (sábado), tanto para a modalidade Iniciação quanto para a modalidade Programação. Todas as provas seão aplicadas no período da tarde.

Copiado de: Site da OBI

Vamos participar, né? Estou ensinando meu amigo Ivo a programar e estou disposto a ajudar a quem quiser; para mim é um prazer ensinar coisas que eu gosto a quem está disposto a aprender.

E sobre a IOI…

Em 2006 a IOI será realizada no México, de 13 a 20 de agosto. Quatro competidores da Modalidade Programação, Nível 2, representarão o Brasil. Você pode ser um deles! [é a minha meta pro ano!]


Fiquei mais de um mês sem postar (ando sem inspiração). Desculpem-me por deixar a Série Algoritmos parada e não noticiar alguns fatos legais que aconteceram enquanto eu não postei (eu parei de escrever, mas comecei a ler mais). Vou voltar de leve agora…

Tenho estudado bastante matemática e física… pras olimpíadas, pro vestibular do ITA, pra desenvolver minha mente, etc. No fundo, é só porque eu gosto mesmo! :) Além disso, o ano letivo começou. Estou com um monte de coisa pra fazer (ainda não estou acostumado com o ritmo), mas vou voltar a postar ativamente.

De qualquer maneira, a pausa foi boa para uma reflexão pessoal e político-filosófica. Essas mudanças meus leitores provavelmente perceberão nos próximos artigos [ou talvez não].

Representando Grafos na Programação

No último artigo, conhecemos a representação chamada “grafo” da seguinte maneira:

Grafo desenhado

Como todos sabemos, seria bem difícil trabalhar uma árvore assim na programação! Por isso, existem várias maneiras de representar um grafo. Nesta série só vou usar as duas mais populares:

  • Matriz de Adjacência
  • Lista de Adjacência

Poderíamos falar também sobre a Matriz de Incidência, mas eu nunca precisei utilizá-la, então prefiro só entrar nessas duas mesmo.

Cada vértice é um número

Para representar um grafo, cada vértice sempre vai ser um número. No caso de você querer representar amizade entre duas pessoas, como no exemplo do Orkut no último artigo, você cria um vetor chamado nome[] que contém o nome de cada número…

  1. Eu
  2. João
  3. Maria
  4. José
  5. Pedro

Matriz de Adjacência

A matriz de adjacência é uma matriz de N x N (onde N é o número de vértices do grafo). Ela inicialmente é preenchida toda com 0 e quando há uma relação entre o vértice do x (número da coluna) com o do y (número da linha), matriz[x][y] é marcado um 1.

Vamos escrever este grafo aqui usando uma matriz de adjacência:

Matriz Inicial

1 2 3 4 5
1
2
3
4
5

Relações do nosso grafo

Já que o grafo não é orientado, a relação 1–2 significa 2–1 também.

  • 1–2 (2–1)
  • 1–3 (3–1)
  • 2–3 (3–2)
  • 2–4 (4–2)
  • 4–5 (5–4)

Essas são as cinco arestas do nosso grafo. Vamos representá-la na matriz de adjacência:

1 2 3 4 5
1 1 1
2 1 1 1
3 1 1
4 1 1
5 1

Simetria

Uma das características da matriz de adjacência quando o grafo não é orientado é a simetria encontrada na “diagonal”. É interessante que se lermos uma coluna de índice v ou uma linha de índice v vamos encontrar a mesma coisa.

Problemas da OBI

Alguns desses programas são complicados, mas isto não entra em questão. Apenas dê uma olhada no recebimento da entrada deles. Todos estão em C. O que eles têm em comum é a utilização de uma matriz de adjacência para guardar o grafo (geralmente nomeada g):

* – Grafo orientado
+ – Grafo ponderado (veremos no próximo artigo)
X – Não queira ver esse problema. Nunca vi solução mais feia. Já estou providenciando uma implementação mais decente… ;)

Descobrir o grau de cada vértice

Eu não disse pra vocês que era fácil conseguir emprego no Orkut? Hehehe… Vamos pensar como podemos descobrir o grau (relembrando… o número de arestas que cada vértice tem OU o número de amigos que cada pessoa tem) na matriz de adjacências. Não basta contar quantos 1s tem na sua linha correspondente? Então façamos isto.

para i \leftarrow{} 1 até N, faça
grau \leftarrow{} 0
para j \leftarrow{} 1 até N, faça
  se matriz[i][j] = 1, então
   grau \leftarrow{} grau + 1
  fim-se
fim-para
imprima “O vértice ” i ” tem grau ” grau ”.”
fim-para

O custo é Θ(n2)\Theta{}(n^{2}) até no melhor caso… Será que não há uma maneira mais simples de fazer isso? Imagina um negócio do tamanho do Orkut. Milhões de pessoas… Não seria bem mais fácil se ao invés de termos que passar por todos os vértices, só passarmos pelos amigos? Não poderíamos colocar somente seus amigos num vetor? É por isto que utilizamos a lista de adjacência.

Lista de Adjacência

A lista de adjacência consiste em criar um vetor para cada vértice. Este vetor contém cada vértice que o vértice “conhece” (tem uma aresta para). Geralmente é representada com uma matriz, porque cada vértice vai precisar de um vetor diferente, não é? Já que eu não vou ser duas vezes “amigo” de ninguém, então podemos fazer uma matriz de NxN.

1 2 3 4 5
1
2
3
4
5

A lista consiste em escrever para cada número de linha (= vértice) seus amigos, da seguinte maneira:

  1. 2, 3
  2. 1, 3, 4
  3. 1, 2
  4. 2, 5
  5. 4

Portanto, na programação seria representado da seguinte maneira:

1 2 3 4 5
1 2 3
2 1 3 4
3 1 2
4 2 5
5 4

O método da lista de adjacências tem várias vantagens: dependendo de como você implementa você não precisa inicializar a lista (zerar), as buscas são bem mais rápidas (você só passa pelos vértices que são “amigos” do atual) e geralmente você já tem o grau do vértice na ponta da língua (eu, pelo menos, sempre uso um vetor cont que contém o número de amigos de cada vértice para ficar mais fácil inserir o próximo elemento na lista – lista[cont[v]++]=w).

Como implementar

Vamos trabalhar com uma entrada de vários x, y, indicando relação entre x-y (y-x) até que x=0 e y=0. O grafo não é orientado.

Matriz de Adjacências

para i \leftarrow{} 1 até N, faça
para j \leftarrow{} 1 até N, faça
  matriz[i][j] \leftarrow{} 0
fim-para
fim-para

enquanto (recebe x, y e x \neq{} 0), faça
matriz[x][y] \leftarrow{} 1
matriz[y][x] \leftarrow{} 1
fim-enquanto

Tem vários exemplos implementados em C [aqui][14].

Lista de Adjacências

para i \leftarrow{} 1 até N, faça
grau[i] \leftarrow{} 0
fim-para

enquanto (recebe x, y e x \neq{} 0), faça
lista[x]grau[x]++] \leftarrow{} y
lista[y]grau[y]++] \leftarrow{} x
fim-enquanto

Para quem não programa em C, o variavel++ significa “incrementar variavel depois da instrução atual”.

As duas juntas

para i \leftarrow{} 1 até N, faça
para j \leftarrow{} 1 até N, faça
  matriz[i][j] \leftarrow{} 0
fim-para
grau[i] \leftarrow{} 0
fim-para

enquanto (recebe x, y e x \neq{} 0), faça
matriz[x][y] \leftarrow{} 1
matriz[y][x] \leftarrow{} 1
lista[x]grau[x]++] \leftarrow{} y
lista[y]grau[y]++] \leftarrow{} x
fim-enquanto

Qual a representação que devo utilizar?

Isso depende totalmente do problema. Em alguns casos, o mais barato é usar as duas representações juntas. As vantagens da lista de adjacências eu já escrevi [aqui][15]. A única vantagem da matriz de adjacências é você em tempo constante (não é nem linear) saber se um vértice é amigo de outro. Afinal, basta testar matriz[v][w].

Até maio do ano passado, eu não tinha aprendido isso direito e sempre usava a matriz de adjacências. Por isso muitos dos meus problemas estão resolvidos de forma pouco eficiente… e isso pode ser crucial numa prova. Por isso, saiba usar as duas formas!

Saber Programar

Este artigo pode ter ficado meio confuso… Acho que acabei me perdendo no meio… :S Deixe um comentário se quiser discutir. ;)


Lógica da Programação

Estava me perguntando hoje:
“O que é a lógica da programação?”
E não consegui obter nenhuma resposta
com exatidão.
Afinal, o que é esse negócio
que “todo programador tem que saber”
e que até cursos existem
para o profissional aprender?

Desculpem… Não pude resistir… Hehehe :D Escrevi algo parecido e coube direitinho, aí dei uma modificada pra ficar em forma de “poesia”. Mas vamos ao artigo…

Talvez minhas idéias sejam diferentes das de quem já fez curso e aprendeu formalmente esse conceito, mas hoje estava pensando e cheguei a conclusão de que a lógica da programação é a lógica da vida, nua e crua. É simplesmente a lógica matemática/filosófica que já conhecemos há tempo e que aprendemos com experiências na vida. Então pra que estudá-la como sendo algo novo?

Um exemplo de lógica da programação, no meu ponto de vista, poderia ser o seguinte:

Eu não gosto de quem mexe nas minhas coisas. E O meu irmão mexeu nas minhas coisas. Disso, deduzimos que eu não gosto do meu irmão. Não importa se é verdadeiro, é lógico. É assim que o computador pensa, não é mesmo? A lógica portanto, para mim, é uma relação entre duas ou mais coisas.

Lógica Matemática

Aí lembrei-me que da lógica matemática, algo que não aprendi formalmente, mas, bom… O Professor Vavá me mencionou no meio desse ano, num dos nossos encontros matemáticos. Ele, decepcionado, mencionou que estava ensinando “lógica” para uma turma de sexta série e passou uma prova com a seguinte questão: As duas afirmações a seguir são corretas: ‘Todo professor de matemática é bonito.’ e ‘O Fabiano é professor de matemática.’ Sabendo disso, o que podemos deduzir? E aí tinham opções como: O Fabiano é feio., O Fabiano é bonito, entre várias outras coisas. Bom… Como é possível alguém errar uma questão dessas?* E, é claro, um monte de gente errou!

Condições Compostas

Cheguei a conclusão de que isto é a lógica da programação. Que é a mesma coisa que a lógica matemática. Aí… Putz… Lembrei-me da semana passada, quando o Leandro Matos (nada contra ele, até tá certo postar artigos assim porque tem gente que consegue ter dificuldade numa coisa dessas!) postou em seu blog um artigo sobre condições compostas – conjunções. Eu, olhando um título como esses, pensei: “Puxa, que esplêndido! Do que será este artigo se trata?” e, antes até de tentar interpretar o título, inocentemente abri uma página para “aprender” sobre o uso do e em condicionais na programação. Realmente decepcionante…

Quer dizer… Poxa! Como é que é possível alguém não entender uma frase como:

n>4 e n<6

… só vai ser verdadeira se 4>n E n<6? Pô, não consigo entender. Já não tá tudo escrito? Não tem nem o que interpretar: basta ler! Então, cheguei a conclusão de que o errado é o que muitas pessoas aprendem como programação. Muitas pessoas não sabem programar. Desculpa se você é um dos caras que tem dificuldade com uma coisa como “condições compostas” (nome bonito, hein?), mas sinceramente, você não vai conseguir programar nada dessa maneira. Você não consegue transpôr um conceito básico da sua vida e das suas experiências pessoais para a programação. Cheguei a conclusão de que as pessoas não entendem o que significa o “verdadeiro” e o “falso” de um condicional, não entendem um ! (“não” no C e linguagens “derivadas”), não entendem que basta você “ler”/“interpretar” a programação. Pô… Olha só a tabela que o Leandro postou no site dele! Hehehe…

É por isso que a gente chama C, PHP e outras siglas de “linguagens”, porque elas não passam de uma maneira diferente de comunicar uma coisa, geralmente muito parecida com o nosso inglês… Aliás, muito parecida com tudo que a gente vê. Quem não entende é porque não tá acostumado com o que a gente vê todos os dias ou faz tanto parte da rotina que nem nota e nem aprende.

Nerds

Então, afinal, quem é um nerd? O programador? Ora… O programador é um ser pensante, ele conhece A Lógica, porque ele tem experiências de vida. Quem não programa direito (não conhece “A Lógica”) é que é nerd, que não tem muitas experiências na vida e não consegue absorver uma mensagem e utilizá-la em outro local. E aí eu fui pensar bem e tentei lembrar-me do que eu considero “nerd”. Sempre me vem imagens de um cara que passa a noite num quarto escuro estourando sua placa aceleradora num jogo viciante e com um fone de ouvido. Nunca me vem a mente um laboratório arrumado, claro, com um programador escrevendo linhas de código.

O exemplo do “Tio Marcos”

E aí, pra completar esse laço gigantesco, lembrei-me do meu tio Marcos. É um cara bem power, o presidente do Conselho Nacional de Psicologia. A gente sempre comenta que o cara sabe “de tudo”. Tipo assim, eu comento sobre software livre, ele responde citando o Linux e com Mozilla. Se um marinheiro vai falar com ele sobre nós de marinheiro, ele sabe um monte sobre isso também! Meu irmão fala com ele sobre música erudita e ele sabe identificar os momentos históricos e qualidades de vários compositores. E a gente sempre chega a conclusão de que ele sabe tudo isso porque ele cria relações entre as coisas, relações adquiridas através de experiências pessoais. Ele já morou na Europa duas vezes (na França e na Espanha), fala essas duas línguas (francês e espanhol), além do português do inglês e vive viajando pelo mundo. Fala com gente de todos os estilos, freqüenta uns locais bem alternativos e dessa maneira é um cara que realmente sabe de “lógica”. Ele tem uma facilidade imensa para relacionar dois conteúdos e tenho certeza que ele não teria dificuldade nenhuma para programar.

Conclusão

E cheguei a conclusão portanto de que lógica é a capacidade que a pessoa tem de relacionar dois eventos. E a lógica é um dos valores mais importantes do homem. Não serve só para a programação, mas para a matemática, para a filosofia e para qualquer coisa que você vá fazer. A lógica não passa do seu raciocínio e do seu pensamento. Aliás, acho que a “inteligência” é uma função que recebe a “lógica” como argumento… ;) Para adquirir lógica, não adianta você ler um monte de coisa; saia do computador e vá conhecer pessoas e adquirir experiências! :D

Concorda? Não concorda? Comente!

* Importante observar que o Professor Vavá nunca elogiaria o seu “Professor Rival”… Na verdade, ele colocou um aviso bem grande na questão: “Deduções lógicas podem às vezes estar erradas, como acontece, por exemplo, nesse caso.” :)

Mini-Poker

Resolvi fazer uma pausa nos algoritmos de ordenação para mostrar como podemos usar os conhecimentos já adquiridos de maneira prática. Vamos neste artigo resolver o problema Mini-Poker, que caiu na prova da Programação Nível 2 (categoria para pessoas até 19 anos ou primeiro ano da faculdade) da Olimpíada Brasileira de Informática de 2005.

Esse post ficou gigante, mas é muito simples. Leia com atenção e acho que você não terá problemas… ;)

Objetivos

Com esta resolução de problema, espero treinar com vocês o conceito de:

  • Interpretação do Probema
  • Entrada e Saída
  • Ordenação por Inserção
  • Pseudocódigo

Acho que será legal para pôrmos em prática o que já estudamos sobre algoritmos.

O problema é bem simples, mas é só pra iniciar.

Enunciado

Mini-Poker é o nome do jogo de cartas que é uma simplificação de Poker, um dos mais famosos jogos de cartas do mundo. Mini-Poker é jogado com um baralho normal de 52 cartas, com quatro naipes (copas, paus, espadas e ouro), cada naipe compreendendo treze cartas (Ás, 2, 3, 4, 5, 6, 7, 8, 9, 10, Valete, Dama, Rei).

No início do jogo, cada jogador recebe cinco cartas. O conjunto de cinco cartas vale um certo número de pontos, de acordo com as regras descritas abaixo. Diferentemente do jogo de Poker normal, em Mini-poker o naipe das cartas é desconsiderado. Assim, para simplificar a descrição do jogo, vamos utilizar os números de 1 a 13 para identificar as cartas do baralho, na ordem dada acima. Uma outra diferença é que pode ocorrer empate entre mais de um vencedor; nesse caso os vencedores dividem o prêmio.

As regras para pontuação em Mini-Poker são as seguintes:

  1. Se as cinco cartas estão em seqüência a partir da carta xx (ou seja, os valores das cartas são xx, x+1x+1, x+2x+2, x+3x+3 e x+4x+4), a pontuação é x+200x+200 pontos. Por exemplo, se as cartas recebidas são 10, 9, 8, 11 e 12, a pontuação é 208 pontos.
  2. Se há quatro cartas iguais xx (uma quadra, ou seja, os valores das cartas são xx, xx, xx, xx e yy), a pontuação é x+180x+180 pontos. Por exemplo, se as cartas recebidas são 1, 1, 1, 10 e 1, a pontuação é 181 pontos.
  3. Se há três cartas iguais xx e outras duas cartas iguais yy (uma trinca e um par, ou seja, os valores das cartas são xx, xx, xx, yy e yy), a pontuação é x+160x+160 pontos. Por exemplo, se as cartas recebidas são 10, 4, 4, 10 e 4, a pontuação é 164 pontos.
  4. Se há três cartas iguais xx e duas outras cartas diferentes yy e zz (uma trinca, ou seja, os valores das cartas são xx, xx, xx, yy e zz), a pontuação é x+140x+140 pontos. Por exemplo, se as cartas recebidas são 2, 3, 2, 2 e 13, a pontuação é 142 pontos.
  5. Se há duas cartas iguais xx, duas outras cartas iguais yy (xyx \neq{} y) e uma outra carta distinta zz (dois pares, ou seja, os valores das cartas são xx, xx, yy, yy e zz), a pontuação é 3×x+2×y+203 \times{} x + 2 \times{} y + 20 pontos, em que x>yx > y. Por exemplo, se as cartas recebidas são 12, 7, 12, 8 e 7, a pontuação é 70 pontos.
  6. Se há apenas duas cartas iguais xx e as outras são distintas (um par, ou seja, os valores das cartas são xx, xx, yy, zz e tt), a pontuação é xx pontos. Por exemplo, se as cartas recebidas são 12, 13, 5, 8 e 13, a pontuação é 13 pontos.
  7. Se todas as cartas são distintas, não há pontuação.

Tarefa

Escreva um programa que, fornecidas as cartas dadas a um jogador, calcule a pontuação do jogador naquela jogada.

Entrada

A entrada é composta por vários casos de teste, cada um correspondendo a uma jogada. A primeira linha da entrada contém um número inteiro NN que indica o número de casos de teste (1N1001 \leq{} N \leq{} 100). Cada uma das NN linhas seguintes contém cinco números inteiros C_1C\_{1}, C_2C\_{2}, C_3C\_{3}, C_4C\_{4} e C_5C\_{5}, representando as cinco cartas recebidas por um jogador (1C_1,C_2,C_3,C_4,C_5131 \leq{} C\_{1}, C\_{2}, C\_{3}, C\_{4}, C\_{5} \leq{} 13).

A entrada deve ser lida do dispositivo de entrada padrão (normalmente o teclado).

Saída

Para cada caso de teste da entrada, seu programa deve produzir três linhas na saída. A primeira linha deve conter um identificador do caso de teste, no formato “Teste n”, onde n é numerado seqüencialmente a partir de 1. A segunda linha deve conter a pontuação do jogador considerando as cinco cartas recebidas. A terceira linha deve ser deixada em branco. A grafia mostrada no Exemplo de Saída, abaixo, deve ser seguida rigorosamente.

A saída deve ser escrita no dispositivo de saída padrã (normalmente a tela).

Restrições

1N1001 \leq{} N \leq{} 100 1C_1,C_2,C_3,C_4,C_5131 \leq{} C\_{1}, C\_{2}, C\_{3}, C\_{4}, C\_{5} \leq{} 13

Exemplo de Entrada

2
12 3 10 3 12
1 2 3 5 4

Saída para o Exemplo de Entrada

Teste 1
62

Teste 2
201

Comentários sobre os problemas de olimpíadas

Todos os problemas passados em competições de programação tem um enunciado parecido com o desse. São especificados todos os limites (restrições), é dito exatamente como será a entrada e como deve ser a saída e geralmente tem uma historinha no começo… :D

Bom… Todos esses dados são fundamentais. Alguns limites nem vamos usar, não tem importância para a nossa solução, mas pode ter importância para outra pessoa que queira implementar um algoritmo diferente. A sintaxe da entrada e da saída são extremamente importantes. Na prova da Seletiva IOI do ano passado, eu quase perdi 60 pontos (6 casos de teste) na solução de um problema simples porque meu programa desprezava um espaço no início de uma frase quando imprimia uma saída. E mesmo a historinha do começo é fundamental. Ela sempre dá boas dicas e algumas vezes até ilustra o problema (às vezes a gente nem lê o enunciado e já sabe que é um problema de grafos!)

Mas vamos a solução deste problema…

Por onde começar?

Com o tempo você pode decidir fazer um caminho diferente, mas eu sugiro começar sempre pelo recebimento da entrada. Aliás, acho que isto é atípico, porque a maioria das pessoas prefere ler bastante o problema e desenvolver todo o algoritmo a mão antes de botar a mão na massa. Eu acho que depois que a gente recebe a entrada, fica bem mais fácil fazer o resto e a gente pode ir pensando enquanto a gente recebe a entrada! Então, depois que lemos o problema e já entendemos tudo o que ele quer, vamos fazer a entrada!

O problema fala que começa nos dando um número N que será o número de casos de teste que teremos que receber depois. Sem dificuldade podemos escrever o _pseudo_código a seguir:

recebe N
para nteste \leftarrow{} 1 até N, faça
fim-para

Já chamo a variável que loopa como nteste, porque já li a saída do problema e sei que vou precisar imprimir o número de caad caso de teste… ;)

Aí o enunciado diz que Cada uma das NN linhas seguintes contém cinco números inteiros C_1C\_{1}, C_2C\_{2}, C_3C\_{3}, C_4C\_{4} e C_5C\_{5}, representando as cinco cartas recebidas por um jogador (1C_1,C_2,C_3,C_4,C_5131 \leq{} C\_{1}, C\_{2}, C\_{3}, C\_{4}, C\_{5} \leq{} 13). Então, vamos receber os cinco números em cada iteração e colocá-los num vetor, é claro!

recebe N
para nteste \leftarrow{} 1 até N, faça
recebe C1,C2,C3,C4,C5C_{1}, C_{2}, C_{3}, C_{4}, C_{5}
fim-para

E a entrada está pronta.

Desenvolvimento

O programa se baseia em encontrarmos valores iguais nos elementos do vetor. O que podemos fazer para facilitar essa tarefa?

Isso mesmo: A ordenação! :D Se os elementos estiverem ordenados, ficará bem mais fácil para procurarmos quatro números iguais, porque eles não poderão ser qualquer uma das possibilidades, mas somente C1,C2,C3,C4C_{1}, C_{2}, C_{3}, C_{4} ou C2,C3,C4,C5C_{2}, C_{3}, C_{4}, C_{5}.

Aí que algoritmos devemos implementar para ordenar? Isso é uma conclusão que vamos chegar no final de nossa série, mas para este algoritmo não tem solução melhor que a Ordenação por Inserção. É um caso pequeno (n=5) e a Ordenação por Inserção é mais rápida que a por Seleção, porque o seu melhor caso é uma função linear. Então, vamos implementar o Insertion Sort no nosso algoritmo:

recebe N
para nteste \leftarrow{} 1 até N, faça
recebe C1,C2,C3,C4,C5C_{1}, C_{2}, C_{3}, C_{4}, C_{5}
// início da ordenação por inserção
para j \leftarrow{} 2 até 5
  elemento \leftarrow{} CjC_{j}
  i \leftarrow{} j-1
  enquanto i > 0 e CiC_{i} > elemento, faça
   Ci+1C_{i+1} \leftarrow{} CiC_{i}
   ii \leftarrow{} Ci1C_{i-1}
  fim-enquanto
  Ci+1C_{i+1} \leftarrow{} elemento
fim-para
// fim da ordenação por inserção
fim-para

O bom desses algoritmos de ordenação é que sua lógica é muito simples e por isso é fácil decorá-los… Ao menos o Insertion Sort e o Selection Sort são algoritmos básicos que todo programador deve conhecer bem. Bom… Acredito que vocês não tenham tido dificuldade pra entender até aqui. A cor vermelha no pseudocódigo eu vou usar daqui pra frente para um comentário, que aliás, é uma excelente prática de boa programação.

O resto do problema precisa calcular quantos pontos o cara fez, baseado em suas cartas, agora já ordenadas. Para isto vamos criar uma função para testar vários se e retornar o resultado.

Eu poderia tirar os se aninhados, mas assim fica mais fácil a compreensão.

Como vamos ver com os pseudocódigos a seguir, é fácil testar cada uma das regras com o vetor ordenado:

Primeira Regra – Seqüência

Se as cinco cartas estão em seqüência a partir da carta xx(ou seja, os valores das cartas são xx, x+1x+1, x+2x+2, x+3x+3 e x+4x+4), a pontuação é x+200x+200 pontos. Por exemplo, se as cartas recebidas são 10, 9, 8, 11 e 12, a pontuação é 208 pontos.

se C1=C21C_{1} = C_{2}-1 e C2=C31C_{2} = C_{3}-1 e C3=C41C_{3}=C_{4}-1 e C4=C51C_{4}=C_{5}-1, então
retorna C1+200C_{1}+200
fim-se

Segunda Regra – Quadra

Se há quatro cartas iguais xx (uma quadra, ou seja, os valores das cartas são xx, xx, xx, xx e yy), a pontuação é x+180x+180 pontos. Por exemplo, se as cartas recebidas são 1, 1, 1, 10 e 1, a pontuação é 181 pontos.

se C1=C2=C3=C4C_{1} = C_{2} = C_{3} = C_{4} ou C2=C3=C4=C5C_{2} = C_{3} = C_{4} = C_{5}, então
retorna C2+180C_{2}+180
fim-se

Aqui retornamos C_2C\_{2} porque ele será sempre parte da quadra (ela começando em C_1C\_{1} ou C_2C\_{2}).

Terceira e Quarta Regra – Trinca

Se há três cartas iguais xx e outras duas cartas iguais yy (uma trinca e um par, ou seja, os valores das cartas são xx, xx, xx, yy e yy), a pontuação é x+160x+160 pontos. Por exemplo, se as cartas recebidas são 10, 4, 4, 10 e 4, a pontuação é 164 pontos.

se C1=C2=C3C_{1} = C_{2} = C_{3} ou C2=C3=C4C_{2} = C_{3} = C_{4} ou C3=C4=C5C_{3} = C_{4} = C_{5}, então
se ( C1C3C_{1} \neq{} C_{3} e C1=C2C_{1} = C_{2} ) ou ( C3C5C_{3} \neq{} C_{5} e C4=C5C_{4} = C_{5} ), então
  retorna C3+160C_{3}+160

Se há três cartas iguais xx e duas outras cartas diferentes yy e zz (uma trinca, ou seja, os valores das cartas são xx, xx, xx, yy e zz), a pontuação é x+140x+140 pontos. Por exemplo, se as cartas recebidas são 2, 3, 2, 2 e 13, a pontuação é 142 pontos.

senão
  retorna C3+140C_{3} + 140
fim-se
fim-se

Note que aqui retornamos C_3C\_{3} porque ele será sempre parte da trinca (o mesmo motivo que retornarmos C_2C\_{2} para a quadra).

Quinta Regra – Duas Duplas

Se há duas cartas iguais xx, duas outras cartas iguais yy (xyx \neq{} y) e uma outra carta distinta zz (dois pares, ou seja, os valores das cartas são xx, xx, yy, yy e zz), a pontuação é 3×x+2×y+203 \times{} x + 2 \times{} y + 20 pontos, em que x>yx > y. Por exemplo, se as cartas recebidas são 12, 7, 12, 8 e 7, a pontuação é 70 pontos.

se C1=C2C_{1} = C_{2} ou C2=C3C_{2} = C_{3}, então
se C3=C4C_{3} = C_{4} ou C4=C5C_{4} = C_{5}, então
  retorna 3×C4+2×C2+203 \times{} C_{4} + 2 \times{} C_{2} + 20
fim-se
fim-se

C_2C\_{2} será sempre elemento da menor dupla e C_4C\_{4} será sempre elemento da maior dupla. Por isso usamos eles como yy e xx, respectivamente.

Sexta Regra – Dupla

Se há apenas duas cartas iguais xx e as outras são distintas (um par, ou seja, os valores das cartas são xx, xx, yy, zz e tt), a pontuação é xx pontos. Por exemplo, se as cartas recebidas são 12, 13, 5, 8 e 13, a pontuação é 13 pontos.

se C1=C2C_{1} = C_{2} ou C2=C3C_{2} = C_{3}, então
retorna C2C_{2}
senão se C3=C4C_{3} = C_{4} ou C4=C5C_{4} = C_{5}, então
retorna C4C_{4}
fim-se

Separei em dois SEs porque senão não saberíamos que valor retornar.

Sétima Regra

Se todas as cartas são distintas, não há pontuação.

retorna 0

Função Inteira

Juntando todos os SEs, temos:

função pontua (C)
// primeira regra
se C1=C21C_{1} = C_{2}-1 e C2=C31C_{2} = C_{3}-1 e C3=C41C_{3}=C_{4}-1 e C4=C51C_{4}=C_{5}-1, então
  retorna C1+200C_{1}+200
fim-se

// segunda regra
se C1=C2=C3=C4C_{1} = C_{2} = C_{3} = C_{4} ou C2=C3=C4=C5C_{2} = C_{3} = C_{4} = C_{5}, então
  retorna C2+180C_{2}+180
fim-se

//terceira e quarta regra
se C1=C2=C3C_{1} = C_{2} = C_{3} ou C2=C3=C4C_{2} = C_{3} = C_{4} ou C3=C4=C5C_{3} = C_{4} = C_{5}, então
  se ( C1C3C_{1} \neq{} C_{3} e C1=C2C_{1} = C_{2} ) ou ( C3C5C_{3} \neq{} C_{5} e C4=C5C_{4} = C_{5} ), então
   retorna C3+160C_{3}+160
  senão
   retorna C3+140C_{3} + 140
  fim-se
fim-se

// quinta regra
se C1=C2C_{1} = C_{2} ou C2=C3C_{2} = C_{3}, então
  se C3=C4C_{3} = C_{4} ou C4=C5C_{4} = C_{5}, então
   retorna 3×C4+2×C2+203 \times{} C_{4} + 2 \times{} C_{2} + 20
  fim-se
fim-se

// sexta regra
se C1=C2C_{1} = C_{2} ou C2=C3C_{2} = C_{3}, então
  retorna C2C_{2}
senão se C3=C4C_{3} = C_{4} ou C4=C5C_{4} = C_{5},
então
  retorna C4C_{4}
fim-se

// sétima regra
retorna 0
fim-função

Já que a função retorna assim que encontra um resultado, não há risco de ocorrer nada errado (por exemplo, uma quadra é sempre uma trinca, que é sempre uma dupla). Agora basta colocarmos esta função no nosso código e adaptar para a saída ser igual a que o problema pede.

Saída

Para chegar a saída, basta fazermos o programa imprimir Teste nteste e depois o retorno da função pontua. Com isto, temos:

recebe N
para nteste \leftarrow{} 1 até N, faça
recebe C1,C2,C3,C4,C5C_{1}, C_{2}, C_{3}, C_{4}, C_{5}
// início da ordenação por inserção
para j \leftarrow{} 2 até 5
  elemento \leftarrow{} CjC_{j}
  i \leftarrow{} j-1
  enquanto i > 0 e CiC_{i} > elemento, faça
   Ci+1C_{i+1} \leftarrow{} CiC_{i}
   ii \leftarrow{} Ci1C_{i-1}
  fim-enquanto
  Ci+1C_{i+1} \leftarrow{} elemento
fim-para
// fim da ordenação por inserção

imprime “Teste ”
imprime linha testen
imprime linha pontua(C)
imprime linha
fim-para

Fiz essa saída assim pra se parecer com Pascal, mas para cada linguagem ela pode ser bem diferente… Vejamos dois exemplos…

C

printf("Teste %d\n%d\n\n", nteste, pontua(C));

PHP

echo "Teste ".$nteste."\n".pontua($C)."\n\n";

Comentários sobre o problema

Este problema é muito chato. É trivial, mas perdemos um tempo enorme escrevendo ses. Ninguém gosta de um problema como esse, mas quando cai numa olimpíada somos obrigados a resolver… hehehe… Mas, para a felicidade geral de todos, saibam que a maioria dos problemas de olimpíadas não são assim. Exigem mais lógica e menos código. Com o tempo, vamos pegando problemas mais difíceis. Espero só ter cumprido meu objetivo dando uma utilidade pra ordenação, entrada e saída e que vocês tenham entendido tudo.

Sugiro que quem esteja aprendendo algoritmos com meus artigos e já saiba programar um pouquinho, resolva alguns problemas simples do site da OBI, que separei especialmente pra vocês!

E, gostaria de fixar, mais importante é a interpretação e o seu pensamento… Programar é fácil!

© 2005–2020 Tiago Madeira