Google Code Jam 2014 ... con obstáculos - Qualification Round

Preparando el concurso

Dado que la entrada y salida de datos es siempre exactamente igual he hecho una plantilla que usaré en todos los retos:

# -*- coding: utf-8 -*-
#!/usr/bin/python
import sys
import time


def getValue(inputFile, dataType="string"):
    line = ""
    value = 0
    while len(line) < 1:
        line = inputFile.readline()

    if dataType == "string":
        value = line.split()[0]
    elif dataType == "integer":
        value = int(line.split()[0])
    elif dataType == "float":
        value = float(line.split()[0])

    return value


def getList(inputFile, dataType="string", separator="space"):
    # dataType could be string, integer and float
    # separator can be space or none
    datalist = []

    line = ""
    while len(line) < 1:
        line = inputFile.readline()

        if  separator == "space":
            datalist = line.split()
        elif separator == "none":
            line = line.rstrip('\n')
            for i in range(len(line)):
                datalist.append(line[i])

    #if dataType=="string": ... already a string
    if dataType == "integer":
        i = 0
        while (i < len(datalist)):
            datalist[i] = int(datalist[i])
            i = i + 1
    elif dataType == "float":
        i = 0
        while (i < len(datalist)):
            datalist[i] = float(datalist[i])
            i = i + 1
    return datalist


def getBoard(inputFile, dimX, dimY, dataType="string", separator="none"):
    board = []

    for y in range(dimY):
        board.append(getList(inputFile, dataType, separator))

    return board


def printBoard(board, array="0"):
    #print board
    for i in range(len(board)):
        for j in range(len(board[i])):
            if array == "1":
                sys.stdout.write("[\"" + str(board[i][j]) + "\"]")
            else:
                sys.stdout.write(str(board[i][j]))

    print ("")


### solve it! ###
def solveIt(inputs, values, board):
#    time.sleep(1)
#    print ""
#    print inputs
#    print values
#    print board

    res = []
    i = 0
    j = 1

    res.append(i)
    res.append(j)

    return res

if __name__ == '__main__':
    # Open input and output files
    inputFile = open(sys.argv[1], 'r')
    outputFile = open(sys.argv[2], 'w')

    # Get number of cases
    NoC = int(getValue(inputFile))

    # for each case
    for i in range(NoC):
        # get case data
        inputs = getValue(inputFile, "integer")
        values = getList(inputFile, "integer")
        board = getBoard(inputFile, "integer")
        # solve problem
        res = solveIt(inputs, values, board)

        # write data
        outputFile.write("Case #" + str(i + 1) + ": " + str(res[0] + 1) + " " + str(res[1] + 1) + "\n")
#        print "Case #"+str(i+1)+": "+str(res[0]+1)+" "+str(res[1]+1)

    # Close input and output files
    inputFile.close()
    outputFile.close()

Y pensarás ¿no puedes hacer eso el día del concurso? Pues no porque tengo los minutos contados. 10 minutos pueden suponer la diferencia entre que de tiempo a resolver un problema o no.

Insomnio provocado

4:00

Aprovecho una subida de fiebre del niño para despertarme del todo y valorar la dificultad de los problemas.

Esta vez me voy directo al "Contest scoreboard" donde veo que los mejores tardan 15 minutos en resolver el primero, otros 15 el segundo y alrededor de una hora el tercero y el cuarto así que supongo que los fáciles son el primero y el segundo.

Tras leer el primero, parece más una broma que un problema. El segundo también parece fácil pero necesita de una lectura más profunda del problema y con menos sueño.

Mañana será otro día. Bueno, hoy dentro de un rato será otro día.

Problem A. Magic Trick

7:30

Este problema es facilísimo. Nos dan dos arrays distintos en los que los números cambian de posición y nos piden que busquemos los números de una fila del primer array en otra fila del segundo array.

  • Si se encuentran 2 -> Case #x: Bad magician!
  • Si no se encuentran -> Volunteer cheated!
  • Si se encuentra 1 -> Case #x: x
8:40

Tras un "tengo pis" y un "dale el antibiótico al niño" la solución, utilizando la plantilla como librería es la siguiente contenida en la función solveIt:

#!/usr/bin/python
# -*- coding: utf-8 -*- 
import time
import sys 
import gcjtemplate

### solve it! ###
def solveIt(row1,board1,row2,board2):
  res=[]
  for i in board2[row2-1]:
    if i in board1[row1-1]:
      res.append(i)

  return res

if __name__=='__main__':
  # Open input and output files
  inputFile=open(sys.argv[1],'r')
  outputFile=open(sys.argv[2],'w')

  # Get number of cases
  NoC=int(gcjtemplate.getValue(inputFile))

  # for each case
  for i in range(NoC):
    # get case data
    row1=gcjtemplate.getValue(inputFile,"integer")
    board1=gcjtemplate.getBoard(inputFile,4,4,"integer","space")
    row2=gcjtemplate.getValue(inputFile,"integer")
    board2=gcjtemplate.getBoard(inputFile,4,4,"integer","space")
    # solve problem
    res=solveIt(row1, board1, row2, board2)

    # write data    
    if len(res)==0:
      outputFile.write("Case #"+str(i+1)+": Volunteer cheated!\n")
      print ("Case #"+str(i+1)+": Volunteer cheated!")
    elif len(res)==1:
      outputFile.write("Case #"+str(i+1)+": "+str(res[0])+"\n")
      print ("Case #"+str(i+1)+": "+str(res[0]))
    elif len(res)>1:
      outputFile.write("Case #"+str(i+1)+": Bad magician!\n")
      print ("Case #"+str(i+1)+": Bad magician!")
    else:
      time.sleep(5)
      print "HELP, HELP!"

  # Close input and output files
  inputFile.close()
  outputFile.close()

Problem B. Cookie Clicker Alpha

8:45

En este problema la dificultad está más en comprenderlo que en resolverlo.

Tu objetivo es llegar a tener un número de galletas en el menor tiempo posible. Empiezas con una fábrica de galletas y que te ha costado x galletas. Cuando llegas a esas x galletas puedes elegir entre comprar una fábrica para doblar la capacidad de producción o seguir produciendo como hasta ahora.

Se trata de elegir la mejor estrategia para decir cual es el menor número de segundos que se tarda en llegar a conseguir el número de galletas que queremos.

Hay que tener en cuenta que nos dan unos límites que, aunque en python nos dan igual, en otros lenguajes nos vienen bien para elegir el tipo de datos.

9:09

Se levanta la tropa, les llevo al parque, reunión familiar, comilona, ...

23:43

Estoy a 1:15 de decidir si paso a la siguiente fase o no. En teoría es factible pero no me gusta ir tan corto de tiempo.

00:35

El que nos den 8 minutos para entregar el resultado de nuestro programa en vez de los 4 habituales es un indicador de que puede ser bastante costoso en términos computacionales.

Este problema es de naturaleza recursiva ya que para decidir entre comprar o no comprar otra fábrica de galletas hay que saber qué es lo que pasaría si no se comprara otra o qué pasaría si se comprara otra, en cuyo caso habría que volver a saber qué es lo que pasaría si no se comprara otra o qué pasaría si se comprara otra, ... hasta que viéramos que deja de salir a cuenta comprar otra fábrica.

Sin embargo el problema se puede enfocar desde un punto de vista más lineal si pensamos en qué vamos a basar la decisión final: si con la capacidad de producción que tengo ahora tardo menos en producir las galletas que aumentando la producción en una fábrica más es que no debo comprar más fábricas.

Ignorando el resto de la plantilla que he puesto arriba del todo, la solución es la siguiente:

### solve it! ###
def solveIt(c,f,x):
  res=0.0

  speed=2
  cookies=0.0
  spentSeconds=0

  finish=0
  while finish==0:
    withoutMoreFarms=x/speed

    secondsToBuyFarm=c/speed
    newSpeed=speed+f
    withOneMoreFarm=secondsToBuyFarm+x/newSpeed

    if withOneMoreFarm< withoutMoreFarms:
      spentSeconds=spentSeconds+secondsToBuyFarm
      speed=newSpeed
    else:
      spentSeconds=spentSeconds+withoutMoreFarms
      finish=1
    
#    print "spentSeconds: "+str(spentSeconds)

  res=spentSeconds
  return res

if __name__=='__main__':
  # Open input and output files
  inputFile=open(sys.argv[1],'r')
  outputFile=open(sys.argv[2],'w')

  # Get number of cases
  NoC=int(getValue(inputFile))

  # for each case
  for i in range(NoC):
    # get case data
    c,f,x=getList(inputFile,"float")
    # solve problem
    res=solveIt(c,f,x)

    # write data    
    outputFile.write("Case #"+str(i+1)+": "+str(round(res,7))+"\n")
    #print "Case #"+str(i+1)+": "+str(round(res,7))

  # Close input and output files
  inputFile.close()
  outputFile.close()

Una vez enviada la respuesta al Large Set te indican que:
You have already tried this input set. (Judged at the end of the contest.)
Así que no hay forma de saber si ya tengo los 25 puntos que necesito o no.

¿Mas?

1:00

Pues va a ser que a estas horas lo que me apetece es dormir. Mañana publicaré este post y el resultado del concurso.

9:20

¡He pasado!

Lo que no entiendo muy bien es cómo puede haber gente que lo haya hecho peor todavía que yo y haber pasado también, pero ahí están los resultados.

Para la próxima a mirar la librería multiprocessing que me hará falta para utilizar todos los cores de mi máquina.

Comentarios

Un comentario

Me he reído bastante al leer los obstáculos a los que te enfrentabas y las peripecias que has tenido que hacer para poder compaginar el concurso con tu vida personal en ambas ocasiones (también leí el de 2013).

Yo también estaré en la ronda 1 participando, y te voy a dar unos consejos por si realmente estas interesado en pasar la siguiente ronda:

- En primer lugar olvídate de la librería multiprocessing. Es un concurso algorítmico y te puedo asegurar que las mejoras que obtengas por paralelización no te ayudaran en nada. Aquí se trata de mejorar la eficiencia asintótica del algoritmo empleado, y el utilizar procesamiento en varios núcleos no marcará la diferencia entre pasar o no (a menos que tengas del orden de 100-1000 nodos, que no es el caso).

- Echa un vistazo a las plataformas de programación competitiva para que vayas familiarizándote con los problemas que te podrás encontrar en un concurso de este tipo. De entre todas, te dejo un enlace, a las que desde mi punto de vista, son más relevantes:
- http://community.topcoder.com/tc
- http://www.codechef.com/
- http://codeforces.com/
- http://uva.onlinejudge.org/ (esta es española)

También podrás encontrar bibliografia especializada. Te recomiendo un libro bastante bueno que se titula "competitive programming" de Steven Halim (lo puedes encontrar en pdf sin problema), se lee rápido y te da las pautas básicas para enfocar adecuadamente este tipo de problemas. Por supuesto, echarle un vistazo a los problemas de ediciones pasadas de CodeJam es prácticamente obligado.

Comentarte también, que la siguiente ronda se compone de 3 fases, de las cuales la primera es la más difícil de superar, dado que estarán los mejores. En la segunda y tercera fase es más fácil dado que los mejores ya habrán conseguido la clasificación en la primera fase, y no pueden participar nuevamente los ya clasificados. De todos modos si tienes tiempo, intenta participar en las tres, y maximizarás tus posibilidades (que repito, pasar a la ronda 2 no es nada fácil).

Bueno, y por último desearte mucha suerte, y que avances en el concurso y nos lo sigas contando en tu blog.