Mostrando postagens classificadas por relevância para a consulta google code jam. Ordenar por data Mostrar todas as postagens
Mostrando postagens classificadas por relevância para a consulta google code jam. Ordenar por data Mostrar todas as postagens

terça-feira, julho 21, 2009

Google Code Jam 2009

O suspense está no fim: a página do Google Code Jam foi atualizada! As datas ainda não estão definidas, mas a inscrição deve começar em meados de agosto e a rodada de qualificação será quatro semanas depois.

Aparentemente vão cortar alguns custos, este ano a final nos EUA terá somente 25 pessoas.

Atualização: e-mail recebido do Google Groups "Google Code Jam Announcements":
Code Jam is back!

We're excited to announce Google Code Jam 2009, this year's iteration of
Google's annual programming competition, which offers coders from around the
world an opportunity to solve complex algorithmic problems under time
pressure, using the programming languages and tools of their choice.
The contest will have a new format this year, starting with online rounds
and ending in a 25-person final in our Mountain View, California
headquarters. We're still choosing exact times for everything, but for
planning purposes we wanted to give you this tentative schedule. Please note
that the timing may change:

Early-Mid August: Registration will open.
+4 Weeks: Qualification round
+1 Week: Rounds 1A, 1B, 1C
+1 Week: Round 2
+1 Week: Round 3
November: World Finals in Mountain View

Online rounds begin soon, so start practicing!

The Google Code Jam Team
http://code.google.com/codejam


segunda-feira, julho 21, 2008

Google Code Jam 2008 - Saving the Universe

"Saving the Universe" foi o primeiro problema da etapa de classificação (Qualification Round) do Google Code Jam 2008. Discuto aqui o problema e a minha solução.

O meu post anterior contém um resumo do que é o Google Code Jam. Para ver as regras e o problema completo, veja o site oficial.

Talvez por ser o primeiro da lista, mas principalmente por parecer o mais fácil, este foi o problema que recebeu o maior número de tentativas. Entretanto, ele tem as suas pegadinhas, o que fez com que tivesse a menor taxa de acertos para o "small input".

Descrição do Problema

Este é um daqueles problemas que exigem a interpretação do enunciado e a coragem de assumir algumas premissas. É uma brincadeira em cima de uma lenda urbana que diz que o universo implodirá se alguem pesquisar por 'google' no Google. Limpando o enunciado e olhando com atenção os exemplos fornecidos, ficamos com algo assim:
  • A primeira linha da entrada contém o número de casos de teste (N).
  • Cada caso começa com uma linha contendo o número de máquinas de busca (S). As S linhas seguintes contém os nomes destas máquinas de busca. Em seguida vem uma linha com o número de consultas (Q), as Q linhas seguintes contém as consultas, que são os nomes das máquinas de busca.
  • O objetivo do programa é direcionar as consultas para as máquinas de busca, na ordem de entrada, reduzindo o número de mudanças de máquina e evitando enviar para uma máquina uma consulta com o seu nome. Para isto o programa pode buferizar as consultas antes de enviá-las para a máquina de busca (algo que está implícito nos exemplos mas não explícito no enunciado).
  • A saída do programa para cada caso é o número de mudanças de máquina de busca necessárias.
Resolvendo o Problema

Uma vez entendido o funcionamento desejado, fica claro que é uma questão de buferizar as consultas até que não se consiga mais atendê-las sem mudar de máquina de busca. Em outra palavras, quando o nome de todas as máquinas de busca apareceu nas consultas é hora de mudar.

Usando um exemplo simples. Supondo que as máquinas de busca sejam A B e C e as consultas sejam A A B A B C C A A A B C:
  • o primeiro 'lote' de consultas é A A B A B
  • ao ler o C é preciso fechar o primeiro lote
  • o segundo 'lote' é C C A A A
  • ao ler o B fechamos o segundo lote
  • o último 'lote' seria B C
  • o resultado são 2 mudanças de máquina de busca
Implementado a Solução

Pensei em duas formas de implementar a solução:
  • Ir montando os 'lotes' à medida em que ler as consultas. Quando o tamanho do lote ultrapassa de S-1 é hora de chavear e iniciar um novo lote. Com isto eu poderia ignorar a lista dos nomes das máquinas de busca. Por outro lado, cada consulta lida exige uma busca no lote atual para ver se é um nome novo.
  • Procurar cada consulta lida na lista de máquinas de busca, convertendo-a assim em um índice. Usar este índice para ir 'ticando' as novas e manter um contador de máquinas disponíveis. Quando o contador for baixar de 1 para 0 é hora de chavear.
Acabei adotando a segunda opção por achá-la mais otimizada, além de funcionar corretamente se por acaso uma das consultas não for um nome de máquina de busca. Optei por ordenar a lista de máquinas de busca usando inserção simples para depois fazer buscas binárias. Estes dois algorítmos são bem simples e eu estava confiante em conseguir codificá-los sem erro (o que realmente aconteceu).

Nota: nestes concursos normalmente a melhor opção é sempre usar o algorítmo mais simples que tenha o desempenho desejado. No caso do Code Jam as linguagens e bibliotecas são livres, portanto seria uma vantagem usar uma linguagem que já tivesse pronta estruturas apropriadas para montar a lista e fazer a busca (como C++, Java, C#, etc). Mas não seria tão divertido e gratificante...

O meu Código

O código completo pode ser baixado do scoreboard do Code Jam (lá na posição 1117), reproduzo abaixo apenas as partes interessantes, sem os códigos de depuração e os comentários redundantes. O código foi feito com o Microsoft Visual C++ v6, mas deve rodar em outros ambientes. Os comentários e identificadores estão me inglês (como é tradição na produção científica brasileira*).

O loop básico para tratar os "N" casos (comum para todos os problemas) é óbvio:
scanf ("%d", &nCases);
for (iCase = 1; iCase <= nCases; iCase++)
{
// ... trata um caso
printf ("Case #%d: %d\n", iCase, nSwitches);
}

O tratamente de um caso ficou assim:
for (nS = 0; nS < S; nS++)
{
gets (name);
InsertEngine (name);
}
nSwitches = 0;
unused = nS;
memset (used, ' ', MAX_ENGINES);
scanf ("%d\n", &Q);
for (nQ = 0; nQ < Q; nQ++)
{
gets (name);
se = FindEngine (name);
if (se != -1)
{
if (used[se] == ' ')
{
if (unused == 1)
{
// must switch
nSwitches++;
unused = nS;
memset (used, ' ', MAX_ENGINES);
}
used[se] = '*';
unused--;
}
}
}
Reparar que usei um array de caracteres para marcar as máquinas usadas e não usadas com '*' e ' '. Embora o código acima seja simples, consegui fazer uma besteira e o resultado foi um erro na primeira tentativa de tratar um 'small input'.

A inserção na lista ordenada de máquinas de busca ficou:
void InsertEngine (char *name)
{
int i;

strcpy (Engines[nS], name);
for (i = nS; i > 0; i--)
if (strcmp (Engines[order[i-1]], name) <= 0)
break;
if (i < nS)
memmove (order+i+1, order+i, (nS-i)*sizeof(int));
order[i] = nS;
}
Por último, a minha velha amiga busca binária:
int FindEngine (char *name)
{
int first, last, mid, dif;

first = 0;
last = nS-1;
while (last >= first)
{
mid = (last+first)/2;
dif = strcmp (Engines[order[mid]], name);
if (dif == 0)
return mid;
else if (dif < 0)
first = mid + 1;
else
last = mid - 1;
}
return -1; // not found
}
* Isto vem de um 'causo' contado pelo brilhante físico Richard Feynman. Para uma visita ao Brasil ele preparou uma apresentação em português (Feynman gostava de estudar linguas). Entetanto, todas as apresentações anteriores no evento foram em inglês, o que o levou a se 'desculpar' por desconhecer esta 'tradição' brasileira. Após a apresentação (em português) de Feynman, todos os oradores seguintes apresentaram seu trabalhos também em português.

sábado, julho 19, 2008

Google Code Jam 2008 - Introdução

O Google Code Jam é uma competição de programação. Decidi participar este ano e apresento aqui alguns comentários a respeito.

Competições de Programação

Existem diversas competições de programação por aí. Uma das mais tradicionais é a ICPC, uma competição para estudantes universitários promovida pela ACM, que aqui no Brasil é a Maratona de Programação. No Brasil temos também a Olimpíada Brasileira de Informática, para alunos do segundo grau. Estes sites possuem arquivos de questões passadas e links para competições on-line.

As questões constam de uma descrição e alguns exemplos de entradas e as saídas corretas correspondentes. Para permitir a correção automatizada das provas, são estipulados formatos rígidos para as entradas e saídas e a conferência é feita na base da comparação dos arquivos de saída com um gabarito.

Um guia sobre como participar destas competições pode ser baixado daqui. O inglês do autor às vezes dá uma falhada e tem um tutorial meio longo de C, mas as dicas são relevantes, mesmo as mais óbvias.

Google Code Jam 2008

Esta competição tem algumas características curiosas. Nos exemplos que mencionei acima, a compilação e execução dos programas é feita pela "banca", o que limita as opções de linguagem e compilador. A competição do Google aceita qualquer linguagem e compilador (e até mesmo soluções à mão!).

A avaliação de cada questão consta de duas partes.
  • A primeira ("small input") gera um arquivo de teste pequeno (e mais simples). Após baixar este arquivo o participante tem 4 minutos para enviar o arquivo de saída, que é conferido imediatamente. Em caso de erro (inclusive timeout), o participante pode solicitar vários arquivos (que serão diferentes), mas é penalizado a cada tentativa mal sucedida.
  • Na segunda parte ("large input") o aquivo é maior e envolve casos mais complexos. O arquivo de saída deve ser enviado em até 8 minutos e o resultado só é apresentado no final da etapa. Nesta etapa não tem segunda oportunidade.
Nas duas partes os fontes devem ser enviados junto com os arquivos de saída e ficam à disposição para download por todos ao final da etapa.

Qualification Round

A competição terá várias etapas (detalhes no site). A primeira, que acaba de ser concluída, foi relativamente "leve". Foram três questões, disponíveis por 24h. Para passar para a fase seguinte bastava acertar pelo menos um small input e um large input.

O ranking foi montado considerando 5 pontos por small input correto e 20 pontos por large input correto, o desempate foi feito pelo horário do envio da resposta mais uma penalidade por respostas incorretas na primeira parte.

Meu Desempenho no Qualification Round

Acabei decidindo participar em cima da hora e portanto não fiz nenhuma preparação especial. Optei por usar C pelo fato de ser a linguagem que tenho maior fluência.

Graças ao fuso horário, o horário de competição para mim foi das 20:00 de 16/07 às 20:00 de 17/07. Devido a alguns compromissos pessoais e profissionais, acabei lendo o enunciado da primeira questão às 20:00 mas só trabalhei na resolução dela e da segunda das 21:00 às 24:00. A terceira questão ficou para o dia seguinte, das 18:00 às 20:00 (e não deu tempo, como vou detalhar depois).

Felizmente eu acertei por completo as duas primeiras questões (com uma vacilada na primeira) o que deu pontos suficientes para passar para a próxima fase. Graças ao horário, eu consegui ficar em 1117 dentro dos 6773 que passaram. No total 7154 pessoas fizeram alguma pontuação.

Nos próximos posts vou comentar as três questões e as minhas soluções.

segunda-feira, julho 27, 2009

Preparação e Metas para o Google Code Jam 2009

Confirmada a realização do Google Code Jam 2009, é hora de planejar a minha preparação e definir minhas metas. No meu post após o meu insucesso na segunda rodada do ano passado, eu listei os pontos em que julguei me faltar preparação.

No ano passado eu adquiri uma série de livros (baseado em recomendações em forums e nas avaliações na Amazon) para cobrir alguns destes pontos:
  • The Art and Craft Of Problem Solving, de Paul Zeitz: um livro sobre estratégias, técnicas e ferramentas para a solução de problemas. É mais voltado para competições de matemática que de programação.
  • How to Solve It, de G Polya: um texto clássico sobre a solução de problemas. Embora o enfoque seja a matemática, várias resenhas afirmam que o livro pode ser útil para qualquer tipo de problema.
  • Introduction to Algorithms, de Cormem, Leiserson, Rivest e Stein: segunda edição de um livro clássico (conhecido como CLR ou CLRS) . Cobre de bem forma bem completa os algorítimos básicos.
  • Geometry Revised, de Coxeter e Greitzer: um livro que cobre pontos normalmente negligenciados no ensino de matemática do ginásio e colegial.
Infelizmente estes livros foram direto para a estante e ficaram lá até agora. Faltando seis a oito semanas para a rodada de qualificação do Google Code Jam 2009, é impossível ler todos com a devida atenção. Pretendo dar pelo menos uma folheada em todos, dando atenção especial ao primeiro da lista.

Junto com este estudo pretendo tentar fazer alguns dos problemas restantes do ano passado (disponíveis aqui) e de outras competições como Maratonas de Programação dos anos passados (aqui) assim como os disponíveis nos arquivos de problemas listados na Wikipedia. Na medida em que conseguir e o tempo deixar vou postar as soluções aqui, como forma de fixação para mim e como ajuda ou curiosidade para os outros.

Ao final da competição do ano passado, a minha meta para este ano era participar da rodada on-site brasileira. Infelizmente, este ano esta rodada foi abolida. Minhas metas passam a ser chegar à terceira rodada e conseguir não zerar nela. A julgar pelo meu desempenho no ano passado (passei raspando na primeira rodada e zerei na segunda) são metas bastante audaciosas.

terça-feira, março 02, 2010

Google Code Jam 2010

O calendário da competição Code Jam de 2010 já está no ar! Data importantes:

7/abril a 7/maio: inscrições
7/maio: rodada de qualificação

As regras parecem ser as mesmas do ano passado.


A cobertura do Google Code Jam no blog está aqui.

sexta-feira, maio 07, 2010

Lembrete: Code Jam 2010

A competição de programação Google Code Jam 2010 começa hoje (7 de maio) às 20:00. A rodada de qualificação fica aberta 24h e você pode se inscrever até o final dela.

A cobertura do Google Code Jam no blog está aqui.

segunda-feira, maio 24, 2010

Ainda Vivo no Code Jam 2010

Bem, pelo menos estou melhor que no ano passado. A primeira rodada do Google Code Jam 2010 ocorreu neste fim de semana e consegui passar para a próxima fase.

terça-feira, março 13, 2012

Google Code Jam 2012

Começam hoje, às 16h, as inscrições no Google Code Jam 2012. A inscrição pode ser feita até o final da rodada de qualificação (que ocorrerá nos dias 13 e 14 de abril).

Detalhes no site oficial: http://code.google.com/codejam/schedule.html

segunda-feira, setembro 07, 2009

Google Code Jam 2009 - Alien Language

Neste post vou discutir o primeiro problema da rodade de qualificação do Google Code Jam 2009. Se você não quiser ver a solução (ou preferir tentar resolver primeiro), pare de ler por aqui e vá para a página oficial.

O Problema

O enunciado completo está, é claro, na página oficial. Segue um resumo:
  • É fornecido um conjunto (dicionário) de D palavras, todas com exatamente L letras minúsculas ('a' a 'z'). No pior caso (large input) D pode ser 5000 e L 15.
  • Devem ser resolvidos N casos de teste. No large input N pode chegar a 500.
  • Em cada caso de teste é preciso responder quantas palavras do dicionário atendem a um determinado padrão.
  • O padrão contem L partes, cada uma correspondendo a uma letra da palavra. Uma parte pode ser uma letra isolada (indicando que somente ela pode ser aceita nesta posição) ou uma sequência de letras cercada por parenteses (indicando que pode ser aceita qualquer uma destas letras nesta posição).
Por exemplo, considerando L = 3, D = 5 e o dicionário

aaa
abc
baa
bbb
xyz

o padrão (ab)a(ac) é atendido por duas palavras (aaa e baa).

Minha Solução

Adotei uma solução "força-bruta", gerando as combinações correspondente ao padrão e procurando-as, uma a uma, no dicionário. Para otimizar a busca no dicionário, ele é ordenado (usando o qsort da biblioteca do C) e feita uma busca binária. Cheguei a pensar em algo mais sofisticado, mas o dicionário é relativamente pequeno (5000 itens requerem no máximo 13 comparações na busca binária).

Minha primeira versão, que gerava todas as combinações, estava muito lenta. Para acelerar, eu passei a testar as combinações parciais e deixei de gerar combinações cujo começo já não estivessem no dicionário. Isto é útil na medida em que o tamanho das palavras aumenta e poucas palavras atendam ao padrão. Não é maravilhoso, mas conseguiu resolver o large input dentro do tempo estipulado (com boa folga).

O meu código ficou assim (qualquer semelhante com a minha solução do primeiro problema do ano anterior é mero cut-and-paste)

//
// Google CodeJam 2009 - Qualification Round, Problem A
// DQuadros
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Dictionary
#define MAX_WORDS 5000
#define MAX_L 15
int nW = 0;
char Words [MAX_WORDS][MAX_L+1];
int order [MAX_WORDS];

// Compare words for qsort
int compWord (const void *elem1, const void *elem2)
{
int i1 = *((int *) elem1);
int i2 = *((int *) elem2);
int dif = strcmp (Words[i1], Words[i2]);
return dif;
}

// Find a partial Word
// (good old binary search)
int FindWordN (char *w, int n)
{
int first, last, mid, dif;

first = 0;
last = nW-1;
while (last >= first)
{
mid = (last+first)/2;
dif = memcmp (Words[order[mid]], w, n);
if (dif == 0)
return mid;
else if (dif < 0)
first = mid + 1;
else
last = mid - 1;
}
return -1; // not found
}

void main (void)
{
int iCase, nCases;
int L, D;
char word [16];
int nHits;
char query [26*15+1];
char qt [15] [27];
int pos [15];
int i, j, k;

scanf ("%d %d %d\n", &L, &D, &nCases);

// Read the dictonary
for (nW = 0; nW < D; nW++)
{
gets (word);
strcpy (Words[nW], word);
order[nW] = nW;
}
qsort (order, nW, sizeof(int), compWord);

for (iCase = 1; iCase <= nCases; iCase++)
{
// Process the query
gets (query);
nHits = 0;
for (i = j = 0; query[i] != 0; i++, j++)
{
if (query[i] == '(')
{
i++;
for (k = 0; query[i] != ')'; i++, k++)
qt [j][k] = query[i];
qt [j][k] = 0;
}
else
{
qt [j][0] = query [i];
qt [j][1] = 0;
}
}

i = 0;
pos[0] = 0;
word[L] = 0;
while (1)
{
word[i] = qt[i][pos[i]];
if (FindWordN (word, i+1) != -1)
{
if (i == (L-1))
{
nHits++;
}
else
{
i++;
pos[i] = -1;
}
}
pos[i]++;
while (qt[i][pos[i]] == 0)
{
i--;
if (i < 0)
break;
pos[i]++;
}
if (i < 0)
break;
}

printf ("Case #%d: %d\n", iCase, nHits);
}

}
Uma Solução Melhor

Uma das coisas legais no Code Jam é que o código fonte dos participantes fica disponível para download, o que permite garimpar soluções interessantes. No caso eu "encontrei ouro" logo na solução de quem ficou em primeiro (que se identifica como 'liszt1990').

Ao invés de gerar as combinações, o que ele faz é testar cada palavra do dicionário contra o padrão. Isto é melhor, pois ele utiliza uma forma muito eficiente para testar se uma palavra atende ao padrão. Cada parte do padrão é expandida para um vetor de 26 posições, cada uma indicando se a letra correspondente é aceita. O teste de uma posição se resume assim a uma indexação.

O código abaixo é a minha implementação desta ideia (o código original de liszt1990 pode ser baixado do site).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Dictionary
#define MAX_WORDS 5000
#define MAX_L 15
int nW = 0;
char Words [MAX_WORDS][MAX_L+1];

void main (void)
{
int iCase, nCases;
int L, D;
int nHits;
char query [28*15+1];
char qt [15] [26];
int i, j;

scanf ("%d %d %d\n", &L, &D, &nCases);

// Read the dictonary
for (nW = 0; nW < D; nW++)
gets (Words[nW]);

for (iCase = 1; iCase <= nCases; iCase++)
{
// Process the query
gets (query);
memset (qt, 0, sizeof(qt));
for (i = j = 0; query[i] != 0; i++, j++)
{
if (query[i] == '(')
{
i++;
for (; query[i] != ')'; i++)
qt [j][query[i]-'a'] = 1;
}
else
{
qt [j][query[i]-'a'] = 1;
}
}

// Check words in dictionary
nHits = 0;
for (i = 0; i < nW; i++)
{
for (j = 0; j < L; j++)
if (qt [j] [Words[i][j]-'a'] == 0)
break;
if (j == L)
nHits++;
}

printf ("Case #%d: %d\n", iCase, nHits);
}

}

quarta-feira, junho 09, 2010

Google Code Jam 2010: Rodada 2

Neste sábado (5/junho) ocorreu a rodada 2 do Google Code Jam 2010. Estavam classificados para a rodada 3000 competidores; apenas os 500 melhores avançaram para a rodada 3. Eu não estava muito otimista quanto a passar (no que estava correto), ainda mais que nas últimas semanas o rítmo no trabalho tem sido intenso (o que explica também a redução dos posts aqui no blog).

Quando começou a rodada, meu primeiro objetivo era ver se tinha algum problema que eu tivesse uma boa chance de resolver; se não tivesse eu ia simplesmente desistir. Foram 4 problemas, com 2,5 horas para solução. Numa primeira olhada os três primeiros problemas pareciam atacáveis, o quarto envolvia uma geometria pesada. Embora os enunciados fossem razoavelmente simples, nos primeiros 20 minutos ninguém tenha resolvido nenhum problema (nem mesmo um small input). A análise oficial dos problemas pode ser vista aqui.

Eu me interessei pelo problema C (Bacteria), uma versão simplificada do clássico Life. Uma solução "força bruta" capaz de resolver o small input parecia simples. E realmente era, mas levei uma hora para implementar. O problema é que o large input era simplesmente imenso. Tive uma ideia para tentar reduzir a memória necessária e gastei uns 50 minutos implementando uma das minhas soluções complexas, cheia de alocação manual de memória. Para minha surpresa, o código funcionou praticamente de primeira... mas se mostrou muito mais lento que a força bruta! Eu cheguei a imaginar uma terceira solução, mas não tinha certeza se conseguiria implementar nos 40 minutos restantes. Dando uma olhada na classificação, percebi que mesmo que conseguisse resolver o large input não ia dar para passar para a rodada seguinte e entreguei os pontos. Pela análise da Google, esta minha solução não deveria ser suficiente.

A rodada foi realmente difícil. Ninguém conseguiu a pontuação máxima. Somente 12 competidores enviaram uma solução para o large input do último problema, e somente 2 acertaram. Somente os seis primeiros acertaram todo o resto (somente um deles tentou o large input do problema D, mas estourou o tempo). O sétimo colocado acertou os problemas A, B e D, mas não teve tempo para o problema C. O último classificado conseguiu acertar o problema B inteiro e mais o small input do C em uma hora e meia, fazendo 31 pontos (o mesmo que eu teria obtido se tivesse conseguido resolver o problema C inteiro).

Minha principal conclusão da minha participação este ano é que usando C puro não vai dar para ir em frente. Para o ano que vem preciso estar preparado para usar uma linguagem com mais recursos.

Sobra a pendência de apresentar minhas soluções para os problemas que eu resolvi na rodada 1. Vamos ver se as coisas acalmam no trabalho e consigo preparar isto.

segunda-feira, maio 10, 2010

Google Code Jam 2010: Qualification Round

O Qualification Round do Code Jam 2010 ocorreu de sexta às 20:00 até sábado no mesmo horário. Trata-se de uma competição de programação que já mencionei várias vezes aqui no blog. Foram três problemas, cada um com dois conjuntos de teste: um pequeno (que você pode tentar várias vezes e é corrigido na hora) e um grande (que você só pode tentar uma vez e só é corrigido no final). Para passar nesta rodada bastava acertar pelo menos um conjunto pequeno e um grande.

quarta-feira, janeiro 20, 2010

Resoluções de Ano Novo - Edição 2010

Com um certo atraso, vamos a minha revisão anual das resoluções do ano passado e às novas resoluções para 2010.

Embora o ano passado tenha sido profissionalmente tranquilo, não tive muito sucesso com minhas resoluções:
  • Melhorar o meu foco e tentar fazer menos coisas ao mesmo tempo: continuo muito dispersivo, o que é um dos motivos de não ter seguido minhas resoluções.
  • Repensar minha forma de trabalho, para colocar manter as pendências sob controle e reduzir o nível de trabalho para algo razoável: Consegui (finalmente) reduzir as horas semanais de trabalho. As pendências não saíram totalmente do controle, mas acho que isto foi devido mais às circunstâncias que a uma mudança na forma de trabalho.
  • Usar com regularidade um sistema de controle de versões: para tentar garantir isto eu deleguei o teste e implantação. Entretanto, não conseguimos fazer funcionar do jeito que a gente queria e acabou encostado.
  • Fazer até o fim pelo menos um dos meus "projetos maravilhosos": pelo menos consegui ir bem longe com alguns projetos, como o spoke-o-dometer e a abóbora assassina.
  • Lançar o meu segundo blog (que não vai ter nada a ver com tecnologia): já registrei no Blogger, tenho um monte de material semi pronto, mas nada de colocar no ar
  • Chegar ao Local Finals do Google Code Jam: nem cheguei perto.
Vamos às "novas" resoluções:
  • Melhorar o foco: mantenho esta resolução. Pretendo fazer uma lista trimestral de metas e resistir às tentações de começar outras coisas antes de terminar as previstas.
  • Achar um jeito de praticar alguma atividade física. Chega de ser sedentário!
  • Melhorar as ferramentas no trabalho: não somente colocar um sistema de controle de versões, mas identificar algumas ferramentas que melhorem a produtividade e a qualidade.
  • Tentar fazer um "projeto maravilhoso" por trimestre. Para quem não olhou as resoluções passadas, me refiro àquelas ideias incríveis que surgem quando estou longe do micro, fico matutando por um tempão mas acaba não tocando em frente.
  • Publicar o segundo blog.
  • Estudar o material que eu andei comprando para me preparar para o Google Code Jam. Não sei se vou participar este ano, mas não dá para deixar estes livros tomando poeira. O primeiro deles já está na mesa de cabeceira.
  • Manter o ritmo do blog: A minha meta é uma média de três posts por semana e não ficar mais de uma semana sem postar (o que é mais ou menos o que eu fiz ano passado).

segunda-feira, abril 11, 2011

Google Code Jam 2011: Inscrições abertas

As inscrições para o Google Code Jam de 2011 já estão com as inscrições abertas no site oficial. Se você não sabe do que estou falando, veja a minha cobertura dos anos anteriores.

As incrições vão até 7 de maio, ao témino da rodada de qualificação iniciada no dia anterior (com duração de 24 horas). Em seguida teremos três rodadas eliminatórias on-line, culminando com a final presencial no Japão. Além da fama, os 1000 melhores colocados na rodada 2 receberão uma camiseta e os 25 participantes da final ganharão prêmios em dinheiro variando de US$10.000 a US$100.

sexta-feira, setembro 04, 2009

Google Code Jam - Qualification Round 2009

Apesar de tudo que andei escrevendo por aqui, acabei me preparando mal para o Google Code Jam. Os aborrecimentos do começo da semana também não ajudaram muito.

De qualquer forma, na quarta feira estava às 20:00 de frente do micro vendo a contagem regressiva para o início da competição. Nesta fase o tempo não é crítico, já que não existe limite do número de pessoas que passam para a fase seguinte e os problemas ficam disponíveis por 24 horas. O requisito para passar é acertar no mínimo um "small input" e um "large input". Para quem não está a par das regras, são três problemas a serem resolvidos. Para cada problema você pode fazer múltiplas tentativas de resolver um conjunto pequeno de dados ("small input") e apenas uma tentativa de resolver um conjunto grande de dados ("large input"). Nos dois casos existe um limite de tempo entre baixar os dados e enviar a resposta. O resultado do small input é validado na hora, o do large input somente no final do período de prova.

Está nos meus planos comentar cada um dos problemas, mas por hoje vou ficar apenas em uma visão geral. Em uma olhada rápida, o primeiro e o terceiro problemas tinham solução "força bruta" óbvia. O segundo problema me pareceu mais complicado.

Comecei pelo primeiro, implementando uma solução "força bruta" com pequenas otimizações. Quando estava com o programa pronto para tentar o "small input", o site começou a apresentar problema no download. Aproveitei a pausa forçada para fazer testes com volume de dados mais próximo do limite do "large input", o que me mostrou que o meu algorítmo estava muito ruim. Após alguns aperfeiçoamentos, consegui baixar o "small input" e o resultado foi aprovado na primeira. Entusiasmado, baixei o "large" e fiquei torcendo para dar tempo. Não foi instantâneo mas ficou dentro do limite e a partir daí era esperar 24 horas (pois o prazo foi dilatado em função dos problemas no download) para saber se estava certo. Neste ponto já tinham passado mais de duas horas, o que é mau sinal para as próximas etapas onde o tempo total para os três problemas é de 2 a 4 horas.

Ataquei em seguida o terceiro problema, novamente pela força bruta. O programa ficou pronto logo, mas se mostrava bastante lento. Como não exergava uma forma de otimizar, resolvi tentar o "small input" e novamente passei de primeira. No entusiasmo, parti para o "large input" e estourei o tempo.

Com as oportunidades reduzidas, resolvi pensar bastante no segundo problema antes de me arriscar. Fui durmir à meia-noite, com algumas idéias vagas. No começo da manhã seguinte comecei a pensar novamente no problema e finalmente achei uma forma inteligente de atacá-lo. O small input passou de primeira e o large input foi processado num piscar de olhos.

Quinta feira, 3 de setembro, 22:00 - a hora da verdade. E a internet cai (devido à chuva)! Após meia hora de espera, consigo acessar novamente o site. E confirmar que acertei os dois "large input" e estou na próxima fase.

Algumas estatísticas:
  • Os primeiros 2425 marcaram 99 pontos (tudo certo)
  • Dois competidores fizeram 89 pontos (erraram somente um small input e acertaram o large input nos acréscimos)
  • Os competidores nas posições 2428 a 3888 fizeram 76 pontos (erraram um large input). Estou aqui, em 2861.
  • Um competidor fez 69 pontos (acertou somente os large inputs!)
  • Os competidores nas posições 3890 a 4823 fizeram 66 pontos (erraram um small e um large)
  • Os competidores nas posições 4824 a 4828 fizeram 56 pontos
  • Os competidores nas posições 4829 a 5154 fizeram 53 pontos
  • Os competidores nas posições 5155 a 5949 fizeram 43 pontos
  • Os competidores nas posições 5950 a 7835 fizeram 33 pontos
  • Daqui para frente estão os que não passaram: 7836 a 7877 (30 pontos), 7878 a 7898 (23 pontos), 7899 a 8035 (20 pontos) e 8036 a 8605 (10 pontos)

quarta-feira, julho 23, 2008

Google Code Jam 2008 - Train Timetable

Continuando a minha apresentação dos problemas do Google Code Jam 2008, discuto agora o segundo problema. Este era o problema mais simples e teve a taxa mais alta de acertos.

Descrição do Problema

A descrição do problema é bastante clara:
  • Trens circulam entre duas estações
  • Após chegar uma estação, um trem precisa de T minutos para poder seguir no sentido contrário
  • Fornecidos todos os horários de partida e chegada dos trens, determinar o número inicial de trens de cada lado para não faltem trens para cumprir os horários.
Um detalhe que complica um pouco é o formato da entrada: para cada estação é fornecida uma lista dos trens que partem dela, com o horário de partida e o horário de chegada na outra estação.

Resolvendo o Problema

Minha solução para o problema consistiu em montar para cada estação uma tabela dos trens partindo e dos trens prontos para partir (levando em conta o horário de chegada e o tempo de virada). Esta tabela é ordenada em primeiro lugar pelo tempo, quando mais de um evento ocorrer no mesmo tempo as partidas devem vir depois (para aproveitar os trens que ficaram prontos no mesmo minuto).

Feitas as tabelas, é só percorrer verificando onde falta trem.

Implementando a Solução

Para facilitar a ordenação, converti os horários para minutos, múltipliquei por 10 e coloquei na unidade um dígito para colocar as partidas depois.
#define MAX_TRIPS       100
#define TRAIN_READY 1
#define TRAIN_DEPARTURE 2

int StationA [2*MAX_TRIPS];
int StationB [2*MAX_TRIPS];
int nSA, nSB;
A conversão de HH:MM para os décimos de minuto é trivial:
int EncodeTime (char *s)
{
return (s[0]-'0')*10*600 + (s[1]-'0')*600 +
(s[3]-'0')*100 + (s[4]-'0')*10;
}
Para cada trem na lista de entrada de uma estação é preciso colocar uma entrada na tabela desta estação (com a partida) e uma entrada na tabela da outra estação (com o horário em que o trem poderá ser reutilizado):
scanf ("%d", &t);
t *= 10;
scanf ("%d %d", &nA, &nB);
nSA = nSB = 0;
for (i = 0; i < nA; i++)
{
scanf ("%s %s", depart, arrive);
Register (StationA, nSA, EncodeTime(depart)+TRAIN_DEPARTURE);
nSA++;
Register (StationB, nSB, EncodeTime(arrive)+t+TRAIN_READY);
nSB++;
}
for (i = 0; i < nB; i++)
{
scanf ("%s %s", depart, arrive);
Register (StationB, nSB, EncodeTime(depart)+TRAIN_DEPARTURE);
nSB++;
Register (StationA, nSA, EncodeTime(arrive)+t+TRAIN_READY);
nSA++;
}
O registro de um evento em uma tabela é feito por inserção simples:
void Register (int *tabEvt, int nEvt, int event)
{
int i;

for (i = nEvt; i > 0; i--)
if (tabEvt[i-1] <= event)
break;
if (i < nEvt)
memmove (tabEvt+i+1, tabEvt+i, (nEvt-i)*sizeof(int));
tabEvt[i] = event;
}
Por último temos a contagem dos trens:
int Trains (int *Station, int nEvt)
{
int i, need, cur;

for (i = need = cur = 0; i < nEvt; i++)
{
if ((Station[i] % 10) == TRAIN_READY)
cur++;
else if (cur == 0)
need++;
else
cur--;
}
return need;
}

segunda-feira, julho 28, 2008

Google Code Jam 2008 - Online Round 1

Proseguindo no Google Code Jam 2008, tivemos neste fim de semana a primeira rodada para valer. E o meu desempenho foi lastimável, apesar de ter passado para a segunda rodada.

A primeira rodada teve as seguintes diferenças em relação à rodada de qualificação:
  • problemas mais difícies
  • três "baterias", com cada participante podendo participar no máximo de uma
  • problemas mais difícies
  • tempo limitado a duas horas
  • problemas mais difícies
  • pontuação diferente para as questões
  • problemas mais ... acho que já deu para pegar a idéia
Quando digo problemas mais difícies, quero dizer que a forma de resolvê-los estava longe de ser óbvia. Além disso, uma solução "força bruta" não tem velocidade suficiente para resolver o "large input".

A primeira bateria que participei foi a 1A, na sexta a noite. E o meu resultado foi o que os tenistas chamam de "pneu" (zero). Eu perdi uns quinze minutos do começo, devido a um problema bobo de login. Dos três problemas, eu cheguei perto de resolver apenas o primeiro (apesar de estar fazendo um complicação desnecessária). Olhando as soluções dos primeiros colocados para os outros dois problemas, não consegui (ainda) entender.

No sábado à tarde tive a segunda e última oportunidade na bateria 1B. Os problemas me pareceram um pouco mais fáceis (ou eu estava com a cabeça mais no lugar). Resolvi atacar primeiro o terceiro problema (que valia mais pontos) pelo método da força bruta. Em pouco mais de trinta minutos eu consegui resolver o "small input", porém chegou perto do limite do tempo (o que significa que não tinha chance para o "large input"). Fiquei algum tempo tentando achar uma forma mais inteligente de resolver o problema, mas não enxerguei. Após gastar mais 30 minutos pensando nos outros dois problemas, resolvi partir para cima do primeiro novamente na base da força bruta. Entretanto, fiz alguma besteira no nervosismo e não consegui passar nem pelo "small input". Olhando as soluções dos primeiros colocados, vi que tinha uma forma bem simples de resolver o primeiro problema. As soluções para os outros dois ainda não entendi.

Graças às pontuações diferentes e o desempate na base do tempo, a minha solução para o terceiro problema (com 15 pontos) me colocou na frente de muita gente que resolveu o primeiro inteiro (5+10 pontos) ou o "small set" do primeiro e do segundo (também 5+10 pontos). Com isto passei para a segunda rodada. Ficou um gosto ruim de ter deixado para trás pessoas que resolveram mais problemas que eu, ainda mais que apelei para a força bruta.

Não acho que faça muito sentido apresentar as minhas soluções. Se tiver disponibilidade de tempo, vou entender as soluções dos primeiros colocados e descrevê-las aqui.

No próximo sábado à tarde tem a segunda rodada, acho pouco provável que eu passe para a terceira.

quarta-feira, junho 16, 2010

Google Code Jam 2010: Making Chess Boards

Making Chess Boards (fazendo tabuleiros de xadrez) foi o terceiro e último problema da rodada !C do Google Code Jam 2010. Segundo a análise oficial, seria uma combinação de parsing, programação dinâmica e truques espertos. Embora eu tenha resolvido com sucesso o problema, minha solução envolve somente parsing e força bruta.

quinta-feira, outubro 23, 2008

Google Code Jam 2008 - Atualização

Após a minha desclassificação, parei de acompanhar o Code Jam 2008. Um dos motivos é só tentar resolver o resto dos problemas depois de concluir o meu "programa de estudos" (e para concluir eu preciso primeiro definí-lo!).

De qualquer forma, o concurso continua e já ocorreram a terceira rodada on-line e as rodadas locais on-site, selecionando os 100 concorrentes que participarão da final nos EUA. A lista dos finalistas pode ser vista aqui. De forma resumida:
  • 43 concorrentes da Europa e Oriente Médio (20 da Rússia)
  • 36 concorrentes da Ásia e Pacífico (21 da China)
  • 21 concorrentes das Américas (14 dos EUA, 2 brazucas e 2 argentinos)
Quem gostar de estatísticas deve dar uma olhada em http://go-hero.net/jam/. Olhando as estatísticas por país, atualizada até a terceira rodada on-line, fico um pouco chateado com o Brasil. O número de concorrentes "qualificados" foi alto (329) mas caiu rapidamente para 9 após a terceira rodada. Em comparação, a China começou com 856 e chegou à rodada local com 109 participantes.

Fica aqui o convite (ou apelo?) para os colegas programadores se prepararem e participarem no ano que vem.

segunda-feira, agosto 02, 2010

Google Code Jam 2010: Encerrado

Na semana passada ocorreu em Dublin a final do Google Code Ja, 2010. O campeão do ano é o russo Egor Kulikov. O campeão dos dois anos anteriores chegou perto mas acabou em quarto.

Nesta última rodada foram seis problemas; ninguém conseguiu resolver completamente os dois últimos.

Os problemas e a análise oficial estão em http://code.google.com/codejam/contest/dashboard?c=801485

quinta-feira, julho 31, 2008

Google Code Jam 2008 - Crop Triangles

Crop Triangles foi o primeiro problema da rodada 1B do Google Code Jam. Embora fosse considerado um problema fácil, pouco mais de um terço dos que enviaram um solução para o large input conseguiram acertar.

O enunciado é bastante enrolado e um exame atento dos exemplos é necessário para entender o que é desejado. Tirando as enrolações, ficamos com o seguinte:

Tem-se uma sequência de N pontos de ccordenada (x,y) gerada pelo seguinte trecho em C:
x = x0;
Y = y0;
for (i = 1; i < n-1; i++)
{
x = (A * x + B) % M;
y = (C * y + D) % M;
}
onde n, x0, y0, A, B, C, D e M são inteiros que variam para cada caso a ser tratado.

O resultado desejado é o número de trios de pontos distintos que tenham a soma dos x e a soma dos y múltiplas de 3.

Pegando o exemplo oficial, com n=4, A=10, B=7, C=1, D=2, x0=0, y0=1 e M=20, temos:
  • os pontos gerados são (0,1), (7,3), (17,5) e (17,7)
  • o único trio que atende à condição estipulada é (0,1) (7,3) (17,5).
Tão importante quanto o enunciado são os limites:
  • número máximo de casos: 10
  • 0 <= A, B, C, D, x0, y0 <= 10^9
  • 1 <= M <= 10^9
  • número máximo de pontos: 100 no small input e 100.000 no large input
Um primeiro fato a lembrar é que um unsigned nos compiladores habituais de 32bits suporta números até 4*1024^3 (ou aproximadamente 4*10^9). Portanto um unsigned é suficiente para os valores, mas não para o cálculo entre eles (A*x ou A*y pode passar de 32bits). Na hora, eu lembrei da primeira parte, mas não da segunda : (.

O segundo fato é que para 100 pontos uma solução força bruta, testando todas as combinações, é viável; para 100.000 pontos vai estourar o tempo.

Solução Força Bruta

A solução força bruta é trivial (exceto para quem esquecer do overflow, como eu):
_int64 n, x0, y0, A, B, C, D, M;

#define MAX_PONTOS 100

struct
{
int x, y;
} Ponto [MAX_PONTOS];

void GeraPontos (void)
{
int i;
int x, y;

x = (int) x0;
y = (int) y0;
for (i = 0; i < n; i++)
{
Ponto[i].x = x;
Ponto[i].y = y;
x = (int) ((A * (_int64) x + B) % M);
y = (int) ((C * (_int64) y + D) % M);
}
}

int ContaTrios (void)
{
int trios = 0;

int i, j, k;

for (i = 0; i < n; i++)
for (j = i+1; j < n; j++)
for (k = j+1; k < n; k++)
{
if ((((Ponto[i].x + Ponto[j].x + Ponto[k].x) % 3) == 0) &&
(((Ponto[i].y + Ponto[j].y + Ponto[k].y) % 3) == 0))
{
trios++;
}
}

return trios;
}
Solução Inteligente

O primeiro ponto a reparar é que não estamos interessados no valor da soma, mas sim no resto da divisão da soma por 3. Portanto tanto faz se o x de um ponto é 1, 4, 7 ou qualquer valor com resto 1, a contribuição para a decisão vai ser a mesma.

O pulo do gato é perceber que uma vez que coordenadas com mesmo resto são iguais, basta contar as ocorrências dos restos, não é preciso guardar as coordenadas:
int cont[3][3];

void GeraPontos (void)
{
int i;
int x, y;

cont[0][0] = cont[0][1] = cont[0][2] = 0;
cont[1][0] = cont[1][1] = cont[1][2] = 0;
cont[2][0] = cont[2][1] = cont[2][2] = 0;

x = (int) x0;
y = (int) y0;
for (i = 0; i < n; i++)
{
cont[x %3][y %3]++;
x = (int) ((A * (_int64) x + B) % M);
y = (int) ((C * (_int64) y + D) % M);
}
}
Montada a tabela de contagem de restos, fica rápido contar os totais. É uma questão de determinar quais combinações produzem soma com resto zero.

Por exemplo, é claro que todas as combinações cont[0][0]*cont[1][1]*cont[2][2] resultam em resto zero. Se pegamos mais de um ponto do mesmo grupo, é preciso fazer o desconto correspondente. Por exemplo, as combinações de três pontos diferentes que tem resto zero nas duas coordenadas é cont[0][0]*(cont[0][0]-1)*(cont[0][0]-2)/6. Adaptando o código java do primeiro colocado (mystic):
__int64 ContaTrios (void)
{
__int64 trios = 0;
int i, j, k;
__int64 ci, cj, ck;

for (i = 0; i < 9; i++)
for (j = i; j < 9; j++)
for (k = j; k < 9; k++)
{
if (((i/3+j/3+k/3) % 3 == 0) &&
((i%3+j%3+k%3) % 3 == 0))
{
ci = cont[i/3][i%3];
cj = cont[j/3][j%3];
ck = cont[k/3][k%3];
if (i < j && j < k)
trios += ci*cj*ck;
else if (i == j && j < k)
trios += (ci * (cj - 1) * ck) / 2;
else if (i [ j && j == k)
trios += (ci * cj * (ck - 1)) / 2;
else
trios += (ci * (cj - 1) * (ck - 2)) / 6;
}
}

return trios;
}
Reparar que novamente o int não é suficiente para armazenar o resultado.