Alcanzar los números de la suerte en reputación

21

Un nuevo jugador de código, Joe, acaba de registrarse en el sitio. Tiene 1 reputación, pero está decidido a alcanzar exactamente todos sus números de la suerte en reputación. Joe cree en poderes superiores que lo ayudarán a lograr su objetivo con una cantidad mínima de acciones (suyas o de otras personas). Como nuevo usuario, también cree que la reputación negativa es posible.

Debería escribir un programa o función que ayude a Joe a calcular cuántas acciones debería esperar.

Detalles

  • Las acciones pueden cambiar la reputación en las siguientes cantidades (todas las acciones están disponibles en cada paso, independientemente de las reglas de intercambio de pila):

    answer accepted:     +15
    answer voted up:     +10
    question voted up:    +5
    accepts answer:       +2
    votes down an answer: −1
    question voted down:  −2
    
  • Otros cambios especiales de reputación no se tienen en cuenta.

  • Los números de la suerte pueden ser negativos y se pueden alcanzar en cualquier orden.
  • Su solución tiene que resolver cualquier caso de prueba de ejemplo en menos de un minuto en mi computadora (solo probaré casos cercanos. Tengo una PC por debajo del promedio).

Entrada

  • Los números de la suerte de Joe como una lista de enteros en una forma común de su idioma.

Salida

  • El número de acciones mínimas necesarias como un solo entero.
  • La salida podría imprimirse en stdout o devolverse como un entero.

Ejemplos

Entrada => Salida (estados de reputación de ejemplo)

1                     => 0  (1)
3 2 1                 => 2  (1 -> 3 -> 2)
2 10 50               => 7  (1 -> 3 -> 2 -> 12 -> 10 -> 25 -> 40 -> 50)
10 11 15              => 3  (1 -> 11 -> 10 -> 15)
42 -5                 => 7  (1 -> -1 -> -3 -> -5 -> 10 -> 25 -> 40 -> 42)
-10                   => 6  (1 -> -1 -> -3 -> -5 -> -7 -> -9 -> -10)
15 -65                => 39
7 2015 25 99          => 142
-99 576 42 1111 12345 => 885

Este es el código de golf, por lo que gana la entrada más corta.

randomra
fuente

Respuestas:

1

C # - 501 bytes

Actualización 551 -> 501 bytes

namespace System.Linq{class A {static void Main(){var i = c(new int[]{10,11,15});Console.WriteLine(i);Console.ReadLine();}private static int c(int[] a){var o=a.OrderBy(x => x).ToArray();int d=1,count=0;for(var i=0;i<a.Length;i++){if(o[i]==d)i++;int c=o[i],b=o.Length>=i+2?o[i+1]-o[i]:3;if(b<=2){i++;c=o[i];}while(d!=c){if(d>c){var e=d-c;if(e>1)d-=2;else d-=1;}else if(c>d){var e=c-d+2;if(e>14)d+=15;else if(e>9)d+=10;else if(e>4)d+=5;else if(e>2)d+=2;}count++;}if(b<=2){d-=b;count++;}}return count;}}}

Código sin golf

namespace System.Linq {
    class Program {
        static void Main(){
            var i = CalculateNumbers(new int[]{10,11,15});
            Console.WriteLine(i);
            Console.ReadLine();
        }
        private static int CalculateNumbers(int[] numbers){
            var ordered = numbers.OrderBy(x => x).ToArray();
            int cur = 1, count = 0;
            for (var i = 0; i < numbers.Length; i++){
                if (ordered[i] == cur) i++;
                int val = ordered[i], next = ordered.Length >= i+2 ? ordered[i + 1] - ordered[i] : 3;
                if (next <= 2){
                    i++;
                    val = ordered[i];
                }
                while (cur != val){
                    if (cur > val){
                        var dif = cur - val;
                        if (dif > 1)
                            cur -= 2;
                        else
                            cur -= 1;
                    } else if (val > cur){
                        var dif = val - cur + 2;
                        if (dif > 14)
                            cur += 15;
                        else if (dif > 9)
                            cur += 10;
                        else if (dif > 4)
                            cur += 5;
                        else if (dif > 2)
                            cur += 2;
                    }
                    count++;
                }
                if (next <= 2){
                    cur -= next;
                    count++;
                }
            }
            return count;
        }
    }
}
Ruben-J
fuente
16

Óxido, 929 923 caracteres

use std::io;use std::str::FromStr;static C:&'static [i32]=&[-2,-1,2,5,10,15];fn main(){let mut z=String::new();io::stdin().read_line(&mut z).unwrap();let n=(&z.trim()[..]).split(' ').map(|e|i32::from_str(e).unwrap()).collect::<Vec<i32>>();let l=*n.iter().min().unwrap();let x=n.iter().max().unwrap()-if l>1{1}else{l};let s=g(x as usize);println!("{}",p(1,n,&s));}fn g(x:usize)->Vec<i32>{let mut s=vec![std::i32::MAX-9;x];for c in C{if *c>0&&(*c as usize)<=x{s[(*c-1)as usize]=1;}}let mut i=1us;while i<x{let mut k=i+1;for c in C{if(i as i32)+*c<0{continue;}let j=((i as i32)+*c)as usize;if j<x&&s[j]>s[i]+1{s[j]=s[i]+1;if k>j{k=j;}}}i=k;}s}fn p(r:i32,n:Vec<i32>,s:&Vec<i32>)->i32{if n.len()==1{h(r,n[0],&s)}else{(0..n.len()).map(|i|{let mut m=n.clone();let q=m.remove(i);p(q,m,&s)+h(r,q,&s)}).min().unwrap()}}fn h(a:i32,b:i32,s:&Vec<i32>)->i32{if a==b{0}else if a>b{((a-b)as f32/2f32).ceil()as i32}else{s[(b-a-1)as usize]}}

¡Esto fue divertido!


Comentario sobre la implementación

Así que obviamente no estoy muy contento con el tamaño. Pero Rust es absolutamente terrible en el golf de todos modos. El rendimiento, sin embargo, es maravilloso.

El código resuelve cada uno de los casos de prueba correctamente en una cantidad de tiempo casi instantánea, por lo que el rendimiento obviamente no es un problema. Por diversión, aquí hay un caso de prueba mucho más difícil:

1234567 123456 12345 1234 123 777777 77777 7777 777

para lo cual la respuesta es 82317, que mi programa pudo resolver en mi computadora portátil (rendimiento medio) en 1.66 segundos (!), incluso con el algoritmo recursivo de ruta Hamiltoniana de fuerza bruta.

Observaciones

  • Primero deberíamos construir un gráfico ponderado modificado, con los nodos siendo cada número "afortunado" y los pesos son cuántos cambios se necesitan para pasar de un nivel de reputación a otro. Cada par de nodos debe estar conectado por dos bordes, ya que subir no es lo mismo que bajar en valor de reputación (puede obtener +10, por ejemplo, pero no -10).

  • Ahora necesitamos descubrir cómo encontrar la cantidad mínima de cambios de un valor de repetición a otro.

    • Para pasar de un valor más alto a un valor más bajo, es simple: simplemente tome ceil((a - b) / 2)donde aestá el valor más alto y bel valor más bajo. Nuestra única opción lógica es usar el -2 tanto como sea posible, y luego el -1 una vez si es necesario.

    • Un valor bajo a alto es un poco más complicado, ya que usar el mayor valor posible no siempre es óptimo (por ejemplo, de 0 a 9, la solución óptima es +10 -1). Sin embargo, este es un problema de programación dinámica de un libro de texto, y un DP simple es suficiente para resolverlo.

  • Una vez que hemos calculado los cambios mínimos de cada número a cualquier otro número, esencialmente nos queda una ligera variante de TSP (problema de vendedor ambulante). Afortunadamente, hay un número suficientemente pequeño de nodos (un máximo de 5 en el caso de prueba más difícil) que la fuerza bruta es suficiente para este paso.

Código no protegido (muy comentado)

use std::io;
use std::str::FromStr;

// all possible rep changes
static CHANGES: &'static [i32] = &[-2, -1, 2, 5, 10, 15];

fn main() {
    // read line of input, convert to i32 vec
    let mut input = String::new();
    io::stdin().read_line(&mut input).unwrap();
    let nums = (&input.trim()[..]).split(' ').map(|x| i32::from_str(x).unwrap())
        .collect::<Vec<i32>>();

    // we only need to generate as many additive solutions as max(nums) - min(nums)
    // but if one of our targets isn't 1, this will return a too-low value.
    // fortunately, this is easy to fix as a little hack
    let min = *nums.iter().min().unwrap();
    let count = nums.iter().max().unwrap() - if min > 1 { 1 } else { min };
    let solutions = generate_solutions(count as usize);

    // bruteforce!
    println!("{}", shortest_path(1, nums, &solutions));
}

fn generate_solutions(count: usize) -> Vec<i32> {
    let mut solutions = vec![std::i32::MAX - 9; count];

    // base cases
    for c in CHANGES {
        if *c > 0 && (*c as usize) <= count {
            solutions[(*c-1) as usize] = 1;
        }
    }

    // dynamic programming! \o/
    // ok so here's how the algorithm works.
    // we go through the array from start to finish, and update the array
    //   elements at i-2, i-1, i+2, i+5, ... if solutions[i]+1 is less than
    //   (the corresponding index to update)'s current value
    // however, note that we might also have to update a value at a lower index
    //   than i (-2 and -1)
    // in that case, we will have to go back that many spaces so we can be sure
    //   to update *everything*.
    // so for simplicity, we just set the new index to be the lowest changed
    //   value (and increment it if there were none changed).
    let mut i = 1us;  // (the minimum positive value in CHANGES) - 1 (ugly hardcoding)
    while i < count {
        let mut i2 = i+1;
        // update all rep-values reachable in 1 "change" from this rep-value,
        //   by setting them to (this value + 1), IF AND ONLY IF the current
        //   value is less optimal than the new value
        for c in CHANGES {
            if (i as i32) + *c < 0 { continue; }  // negative index = bad
            let idx = ((i as i32) + *c) as usize;  // the index to update
            if idx < count && solutions[idx] > solutions[i]+1 {
                // it's a better solution! :D
                solutions[idx] = solutions[i]+1;
                // if the index from which we'll start updating next is too low,
                //   we need to make sure the thing we just updated is going to,
                //   in turn, update other things from itself (tl;dr: DP)
                if i2 > idx { i2 = idx; }
            }
        }
        i = i2;  // update index (note that i2 is i+1 by default)
    }

    solutions
}

fn shortest_path(rep: i32, nums: Vec<i32>, solutions: &Vec<i32>) -> i32 {
    // mercifully, all the test cases are small enough so as to not require
    //   a full-blown optimized traveling salesman implementation
    // recursive brute force ftw! \o/
    if nums.len() == 1 { count_changes(rep, nums[0], &solutions) }  // base case
    else {
        // try going from 'rep' to each item in 'nums'
        (0..nums.len()).map(|i| {
            // grab the new rep value out of the vec...
            let mut nums2 = nums.clone();
            let new_rep = nums2.remove(i);
            // and map it to the shortest path if we use that value as our next target
            shortest_path(new_rep, nums2, &solutions) + count_changes(rep, new_rep, &solutions)
        }).min().unwrap()  // return the minimum-length path
    }
}

fn count_changes(start: i32, finish: i32, solutions: &Vec<i32>) -> i32 {
    // count the number of changes required to get from 'start' rep to 'finish' rep
    // obvious:
    if start == finish { 0 }
    // fairly intuitive (2f32 is just 2.0):
    else if start > finish { ((start - finish) as f32 / 2f32).ceil() as i32 }
    // use the pregenerated lookup table for these:
    else /* if finish > start */ { solutions[(finish - start - 1) as usize] }
}
Pomo de la puerta
fuente
1
Respuesta impresionante! Estoy interesado en Rust y la explicación es realmente muy útil para aprender. Y solo como un aviso, puede obtener resaltado de sintaxis con <!-- language-all: lang-rust -->. ;)
Alex A.
Estoy trabajando en una solución, y vio que la cantidad mínima de cambios en el bajo a alto peso fácilmente se puede calcular en O (1) mediante el uso de una muy pequeña tabla de consulta, como en este C-como pseudo-código floor((a-b)/15)+{0,2,1,2,2,1,3,2,2,2,1,3,2,2,2}[(a-b)%15]. Su solución probablemente podría beneficiarse de esto.
Fors
2

Pyth - 43 42 bytes

Utiliza un enfoque de fuerza bruta completa con todas las permutaciones y combinaciones. No busco más golf porque se traducirá a Pyth. Traducido.

K5tf.Em.Am}kmhs<dbUdQsm.pk.C[15yKK2_1_2)TZ

Esto es incluso más lento que la versión de Python porque uso filtro en lugar de un ciclo while. Explicación próximamente, ahora mira el código de Python.

Pruébalo aquí en línea .

from itertools import*
Q,Z=eval(input()),0
while True:
    if any(map(lambda d:all(map(lambda k:k in map(lambda b:sum(d[:b])+1,range(len(d))),Q)),chain.from_iterable(map(lambda k:permutations(k),combinations_with_replacement([15,10,5,2,-1,-2],Z))))):
        print(Z-1)
        break
    Z+=1

Funciona en los pequeños, no dejó que se ejecute hasta su finalización en los grandes.

Maltysen
fuente
No he leído el código correctamente, pero ¿puedes reemplazar 10 por, digamos, y5para ahorrar en espacios en blanco?
Sp3000
@ Sp3000 ahorraría espacios en blanco pero no caracteres en general. Pero creo que puedo guardar un personaje comprimiendo la lista almacenandoK=5
Maltysen
Tenga en cuenta que esta respuesta no sigue las reglas como "Su solución tiene que resolver cualquier caso de prueba de ejemplo en menos de un minuto". (La cita está en negrita en la sección Detalles.)
randomra
0

C ++ - 863 bytes, sin golf

Esto funciona bastante rápido, en el mismo estadio que la solución escrita en Rust (aproximadamente 6 veces más rápido cuando se compila con la optimización activada). Jugaré golf más tarde esta tarde (tarde en Suecia, claro).

#include <iostream>
#include <vector>
#include <string>
#include <sstream>

const int lookup[] = {0, 2, 1, 2, 2, 1, 3, 2, 2, 2, 1, 3, 2, 2, 2};

int distance(int start, int end) {
    return start > end
        ? (start - end + 1) / 2
        : (end - start) / 15 + lookup[(end - start) % 15];
}

int walk(int current, std::vector<int> points) {
    int min = 0;

    if (points.size() == 0) return 0;

    for (int i = 0; i < points.size(); i++) {
        std::vector<int> new_points = points;
        new_points.erase(new_points.begin() + i);

        int d = distance(current, points[i]) + walk(points[i], new_points);

        min = min && min < d ? min : d;
    }

    return min;
}

int main() {
    std::vector<int> points;

    std::string line;
    std::getline(std::cin, line);

    std::stringstream ss(line);
    int i;

    while (ss >> i)
        points.push_back(i);

    std::cout << walk(1, points) << std::endl;

    return 0;
}
Fors
fuente