Published using Google Docs
Como se tornar um programador competitivo
Updated automatically every 5 minutes

Como se tornar um programador competitivo

Por Matheus Pimenta (matheuscscp@gmail.com).

Leia este tutorial todo de uma vez! E depois leia de novo, e de novo, e de novo…

Por que se tornar um programador competitivo

Os três pilares do programador em uma competição

Entre as várias características que podem ser úteis para um programador durante uma competição, as três descritas nesta seção são especiais no seguinte sentido: a falta de apenas uma delas já é um grande abismo entre você e os resultados de mais destaque (medalha, primeiro lugar, vaga na próxima etapa, vaga no mundial, etc.). Se você acha que não possui algumas destas características, não se preocupe, pois você pode adquirir todas elas com trabalho duro e disciplina.

Conhecimento

Descrição: Conheça em detalhes muitos problemas, algoritmos e estruturas de dados.

Por que adquirir: Justificativa desnecessária.

Como adquirir: Em primeiro lugar, nunca tente adquirir durante uma competição um conhecimento que você não possui previamente e é necessário para resolver o problema, pois isto seria extremamente injusto com os outros competidores e você dificilmente seria capaz de obter este conhecimento em tempo hábil. Com certeza, seria mais produtivo tentar resolver outros problemas. Durante o tempo de treino e aprendizado, não tenha medo de encarar livros avançados sobre algoritmos e estruturas de dados em inglês, nem de encarar blogs em inglês, nem blogs em russo, nem blogs em chinês, nem blogs em japonês, nem de encarar aquele assunto que você desconhece completamente. Além disso, fique atento aos assuntos que estão “na moda”, pois “ondas” de problemas sobre um mesmo assunto são muito comuns. Não tenha medo também de pedir para alguém te ensinar alguma coisa ou te ajudar a resolver um problema, pois algum dia esta situação irá se inverter; e neste momento você deve retribuir a ajuda para aquela pessoa desconhecida que mora em outro país e te ajudou! Não tenha medo de encarar o código bagunçado de outra pessoa, pois muitas vezes é só isso que vai estar ao seu alcance para aprender como se resolve o problema. Não tenha medo de gostar de matemática, teoria e demonstrações formais! Não tenha medo!

Esperteza (experiência)

Descrição: Seja esperto para observar detalhes não-triviais.

Por que adquirir: Competições de programação estão muito longe de destacarem apenas quem tem mais conhecimento e de serem apenas sobre implementação de algoritmos. Pouquíssimos problemas se resumem a uma combinação simples de algoritmos e estruturas de dados. Na verdade, é o contrário! Os problemas que colocam o seu time na frente são aqueles onde a combinação de técnicas não é trivial. É necessário observar um fato, uma propriedade matemática que está completamente escondida nas entrelinhas, e somente então será possível construir uma solução para o problema. Mais que isso, a parte difícil de muitos problemas é apenas encontrar esta observação não-trivial. Por exemplo: “As restrições do problema fazem com que o grafo da entrada seja sempre planar! Então podemos aplicar o teorema das quatro cores.” Aplicar o teorema é provavelmente muito fácil e pode ser que todos do seu time conheçam o teorema. Mas será que alguém vai perceber que todos os grafos do problema são planares? Se ninguém perceber, conhecer o teorema não irá adiantar de nada.

Como adquirir: Esperteza é sinônimo de experiência. Quanto mais problemas você resolver, mais propriedades você irá conhecer. Quanto mais problemas sobre uma mesma propriedade você resolver, mais facilidade você terá para enxergar esta propriedade nas entrelinhas de um novo problema. Quanto mais problemas você resolver, mais “truques” você irá conhecer. E, acredite, existe uma quantidade surpreendente de “pequenos truques” que fazem a diferença em programação competitiva. Um “truque” é uma maneira extremamente rápida e eficiente de fazer alguma coisa que a maioria dos programadores comuns ignora. Por exemplo: como decidir se x é potência de 2? Resposta em C++: (x&(x-1))==0 (pesquise!). Hacker não é quem rouba senha de banco. Hacker é quem tem esperteza e conhece vários “truques”, ou seja, vários hacks. A competição realizada pelo Facebook, por exemplo, o Facebook Hacker Cup, não é uma competição relacionada à segurança computacional. Ela segue os mesmos formatos das competições de programação tradicionais. Portanto, seja hacker! E lembre-se: para observar o detalhe que falta é preciso olhar a situação de todos os pontos de vista. É muito comum fracassar em um problema durante uma competição por “viciar” em um único ponto de vista que não ajuda. Se uma abordagem não está funcionando, tente outra!

Agilidade

Descrição: Tenha agilidade para escrever e depurar código.

Por que adquirir: O seu time pode ter o conhecimento e a esperteza necessários para resolver o problema, mas muitos outros times podem não apenas possuir estas mesmas características como podem também possuir um membro que é vencedor de concursos de digitação. Um cenário bastante frequente onde isto pode ser péssimo é no caso de um conjunto de problemas onde a maior parte é fácil e pouquíssimos são difíceis. Estas provas são consideradas “corridas contra o tempo”, pois, em qualquer competição, um único problema fácil não resolvido é uma enorme desvantagem. E quanto maior for a quantidade de problemas fáceis, mais grave esta desvantagem se torna! E não se engane, pois o conceito de “problema fácil” é extremamente relativo. Por “conjunto de problemas fáceis”, entenda “conjunto de problemas que a grande maioria dos times da competição é capaz de resolver sem dificuldade”. Logo, provas em que este conjunto é a maior parte podem aparecer com mais frequência do que você imagina!

Como adquirir: Durante o tempo de treino, resolva muitos problemas e não reaproveite código de um exercício para o próximo: digite todo o código do zero. Tente cronometrar o tempo que você leva para resolver um problema e tente melhorar este tempo constantemente. Faça isto durante várias simulações de competições (como as participações virtuais oferecidas pelo Codeforces). Durante uma competição, tenha impressa uma biblioteca de macros, algoritmos e estruturas de dados implementados nas suas linguagens de programação preferidas que são permitidas em competições (C++ e Python, claro!). Por exemplo, tenha uma macro para escrever apenas

rp(i,a,b)

no lugar de

for (int i = a; i <= b; i++)

Acredite, faz muita diferença! E lembre-se: para ser ágil é necessário manter a calma. A frieza para terminar de codificar e depurar um programa no último minuto de uma competição pode ser o obstáculo entre o seu time e a vaga no mundial! Uma vez, eu participei de uma competição entre duplas. Perto do fim, nossa dupla estava em segundo lugar e eu estava escrevendo o programa que faltava, era um algoritmo bem simples. Faltando 5 minutos, eu travei por conta do nervosismo. Mas o meu colega conseguiu terminar e enviamos o código faltando exatamente dois segundos para o fim da competição. Ficamos em primeiro lugar! A melhor maneira de treinar agilidade sob pressão é participar de várias competições, como os rounds individuais do Codeforces, por exemplo.

Características adicionais que fazem a diferença

Agenda para se tornar um programador competitivo

Não há uma maneira “correta” de se tornar um programador competitivo, mas se você se sente na posição de um completo novato e não possui nenhuma experiência, talvez seja uma boa ideia ler as sugestões a seguir.

  1. Aprenda o básico de programação. (que se resume a: manipulação e armazenamento de dados, entrada e saída, estruturas condicionais, laços de repetição e funções)

  1. Aprenda a provar/verificar a correção de algoritmos e aprenda a analisar a complexidade de algoritmos. Um algoritmo para um problema é satisfatório se: 1) ele é de fato um algoritmo para o problema, ou seja, ele dá a saída correta para qualquer entrada válida; 2) ele possui complexidade de tempo adequada; e 3) ele possui complexidade de espaço adequada. Pesquise e aprenda o significado destes termos!

  1. Aprenda o que são estruturas de dados e como usá-las (as mais básicas são: array, vector, linked list, stack, queue, priority queue, sets e maps). Cada ED possui uma complexidade de espaço e um conjunto de operações. Cada operação de uma ED é na verdade um algoritmo, ou seja, possui complexidade de tempo e de espaço. Algoritmos são construídos a partir de estruturas de dados.

  1. Aprenda todas as características de uma linguagem de programação popular em competições. As mais populares são C++ e Python e cada uma tem seus próprios motivos para ser usada. Aprenda a utilizar todas as funções e estruturas de dados da biblioteca padrão desta linguagem. Aprenda todas as funções e técnicas para entrada e saída que esta linguagem oferece.

  1. Enquanto completa os estágios de 1 a 4, cadastre-se em algum online judge (por exemplo: URI OJ e UVa OJ) e pratique os conceitos que você está aprendendo, enquanto faz exercícios básicos. Acostume-se com o ambiente e com a formatação dos problemas.

  1. Leia a bíblia: o livro Competitive Programming, dos irmãos Halim. Este livro apresenta a maioria dos assuntos, problemas, algoritmos e estruturas de dados que aparecem em competições de programação, incluindo implementações e explicações detalhadas.

  1. Enquanto estiver lendo o livro dos irmãos Halim, passe um bom tempo exclusivamente em cada assunto, resolvendo vários problemas. Por exemplo: 50 problemas de cada. Aqui vai quase a totalidade dos assuntos que de alguma forma já cruzaram o meu caminho:
  1. Os quatro paradigmas de algoritmos: busca completa; divisão e conquista; algoritmos gulosos; e programação dinâmica.
  2. Problemas ad-hoc (problema de Josephus, 2-SAT, problema das oito rainhas, movimentos do cavalo...)
  3. Busca e ordenação (busca binária, fractional cascading, busca binária paralela, merge sort, contagem de inversões, radix sort, k-th order statistics, wavelet trees...)
  4. Problemas de intervalo (range minimum/maximum/sum query, algoritmos online e offline, algoritmo de Mo, sparse table, decomposição em raiz quadrada...)
  5. Estruturas de dados avançadas (disjoint sets union/union-find, árvores de busca binária, heaps, treaps, segment trees, fenwick trees/binary indexed trees, link-cut trees, Euler tour trees, lazy propagation, estruturas de dados persistentes...)
  6. Strings (autômatos, casamento, prefix function, algoritmo KMP, Z function, trie, algoritmo Aho-Corasick, array de sufixos, hashing...)
  7. Álgebra (conjuntos, relações, funções, operações, grupos, anéis, corpos, polinômios, FFT/NTT, algoritmo baby-step giant-step/meet-in-the-middle, algoritmo de Floyd para ciclos, algoritmo Pollard’s rho...)
  8. Teoria dos números (divisibilidade, números primos, teorema fundamental da aritmética, crivo de Eratóstenes, fatoração, funções sobre divisores, teorema de Fermat-Euler, teorema chinês do resto, equações diofantinas...)
  9. Álgebra linear (espaço vetorial, dimensão e base, espaço linha, espaço coluna, espaço nulo, transformação linear, núcleo e imagem, eliminação gaussiana…)
  10. Análise combinatória (princípio da inclusão-exclusão, fatoriais, binômio de Newton, combinações, arranjos e permutações, anti-fatoriais, stars and bars theorems, números de Catalan, sequências de Prüfer, fórmula de Cayley...)
  11. Aproximações numéricas (processos iterativos, método Newton-Raphson, busca binária, busca ternária, integração numérica, regra do trapézio, regra de Simpson...)
  12. Teoria da probabilidade (princípio da inclusão-exclusão, teorema da probabilidade condicional, teorema de Bayes, média/valor esperado/esperança matemática, variância e desvio padrão, processos estocásticos, cadeias de Markov...)
  13. Geometria computacional (primitivas geométricas e suas propriedades, princípio da inclusão-exclusão, convex hull, line/radial sweep, bin packing, k-d trees...)
  14. Grafos (tipos de grafos, busca em profundidade, busca em largura, componentes e conectividade, arestas de corte/pontes, vértices de corte/articulação, componentes bi-conectadas por vértices/arestas, diâmetro, dominator tree, árvores e florestas, LCA, HLD, small to large/DSU on tree, centroid decomposition, árvore de extensão mínima, algoritmo de Kruskal, MST dinâmico, caminho mínimo, algoritmo de Dijkstra, algoritmo Floyd-Warshall, problema do caixeiro viajante, DAGs, componentes fortemente conexas, algoritmo de Kosaraju, conectividade dinâmica, travessia paralela, caminho Euleriano, caminho Hamiltoniano, redes de fluxo, teorema do fluxo máximo e corte mínimo, método Ford-Fulkerson, algoritmo Edmonds-Karp, algoritmo de Dinic, algoritmo push-relabel/preflow-push, fluxo de custo mínimo, grafos bipartidos, casamento bipartido máximo, cobertura mínima de vértices, conjunto independente máximo, algoritmo dos caminhos de aumento, algoritmo Húngaro Kuhn-Munkres, problema do casamento estável, casamento em grafos quaisquer, Blossom algorithm, coloração de vértices e arestas, grafos planares, teorema de Kuratowski, teorema das quatro cores...)
  15. Teoria dos jogos (Nim game, Sprague-Grundy theorem, Grundy number...)

Para este estágio, utilize os OJs: codeforces, TopCoder e UVa OJ.

  1. Enquanto completa os estágios 6 e 7, participe dos rounds do codeforces. Estas competições duram duas horas, têm por volta de cinco problemas, são feitas de uma até quatro vezes por semana e são planejadas para serem feitas individualmente.

  1. Observe que exercícios que envolvem somente um assunto ajudam a fixar este assunto, mas são exercícios fáceis! Exercícios médios/difíceis são aqueles que combinam diferentes assuntos/técnicas, ou aqueles que não deixam claro qual é o tipo de técnica que deve ser utilizada na solução, ou seja, é necessária uma observação não-trivial para resolver o problema. A melhor maneira de treinar para estes problemas é fazer um time de três pessoas e simular os contests de cinco horas do codeforces. Estas competições têm entre dez e doze problemas, são competições oficiais passadas e foram feitas para se fazer em time. De preferência, encontre pessoas empolgadas para estudar/treinar no mesmo ritmo que você. Assim, se algum integrante do time começar a perder o ritmo de treino, os outros dois vão puxá-lo de volta! Tentem fazer pelo menos uma simulação por semana!

  1. Não pare de resolver individualmente os rounds do codeforces, e também não deixe seus colegas de time pararem.

Conselhos gerais

Links úteis