Atingir números de sorte na reputação

21

Um novo jogador de código, Joe, acabou de se registrar no site. Ele tem 1 reputação, mas determinado a alcançar exatamente todos os seus números da sorte na reputação. Joe acredita em poderes superiores que o ajudarão a alcançar seu objetivo com uma quantidade mínima de ações (dele ou de outras). Como novo usuário, ele também acredita que a reputação negativa é possível.

Você deve escrever um programa ou função que ajude Joe a calcular quantas ações ele deve esperar.

Detalhes

  • As ações podem alterar a reputação pelos seguintes valores (todas as ações estão disponíveis a cada etapa, independentemente das regras de troca de pilha):

    answer accepted:     +15
    answer voted up:     +10
    question voted up:    +5
    accepts answer:       +2
    votes down an answer: −1
    question voted down:  −2
    
  • Outras alterações de reputação especiais são desconsideradas.

  • Os números da sorte podem ser negativos e podem ser alcançados em qualquer ordem.
  • Sua solução precisa resolver qualquer exemplo de caso de teste em menos de um minuto no meu computador (apenas testarei casos encerrados. Tenho um PC abaixo da média.).

Entrada

  • Os números da sorte de Joe como uma lista de números inteiros em uma forma comum do seu idioma.

Saída

  • O número de ações mínimas necessárias como um único número inteiro.
  • A saída pode ser impressa em stdout ou retornada como um número inteiro.

Exemplos

Entrada => Saída (exemplo de reputação)

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

Isso é código-golfe, portanto a entrada mais curta vence.

randomra
fonte

Respostas:

1

C # - 501 bytes

Atualização 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 ungolfed

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
fonte
16

Ferrugem, 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]}}

Isso foi divertido!


Comentário sobre a implementação

Então, obviamente, não estou muito feliz com o tamanho. Mas Rust é absolutamente terrível no golfe de qualquer maneira. O desempenho, no entanto, é maravilhoso.

O código resolve cada um dos casos de teste corretamente em um período quase instantâneo, portanto o desempenho obviamente não é um problema. Por diversão, aqui está um caso de teste muito mais difícil:

1234567 123456 12345 1234 123 777777 77777 7777 777

para o qual a resposta é 82317, que meu programa conseguiu resolver no meu laptop (de médio desempenho) em 1,66 segundos (!), mesmo com o algoritmo do caminho Hamiltoniano de força bruta recursiva.

Observações

  • Primeiro, devemos construir um gráfico ponderado modificado, com os nós sendo cada número "sortudo" e os pesos sendo quantas alterações são necessárias para passar de um nível de reputação para outro. Cada par de nós deve ser conectado por duas arestas, pois subir não é o mesmo que diminuir o valor da reputação (você pode obter +10, por exemplo, mas não -10).

  • Agora, precisamos descobrir como encontrar a quantidade mínima de alterações de um valor de representante para outro.

    • Para passar de um valor mais alto para um valor mais baixo, é simples: basta levar para ceil((a - b) / 2)onde aestá o valor mais alto e bo valor mais baixo. Nossa única opção lógica é usar o -2 o máximo possível e depois o -1 uma vez, se necessário.

    • Um valor baixo a alto é um pouco mais complicado, pois o uso do maior valor possível nem sempre é ideal (por exemplo, de 0 a 9, a solução ideal é +10 -1). No entanto, esse é um problema de programação dinâmica de livros didáticos, e o DP simples é suficiente para resolvê-lo.

  • Depois de calcular as alterações mínimas de cada número para qualquer outro número, ficamos essencialmente com uma ligeira variante do TSP (problema do vendedor ambulante). Felizmente, há um número suficientemente pequeno de nós (no máximo 5 no caso de teste mais difícil) que força bruta é suficiente para esta etapa.

Código não-protegido (fortemente 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] }
}
Maçaneta da porta
fonte
11
Resposta incrível! Estou interessado em Rust e a explicação é realmente muito útil para aprender. E, como um alerta, você pode obter destaque de sintaxe com <!-- language-all: lang-rust -->. ;)
Alex A.
Eu estou trabalhando em uma solução, e de serra que a quantidade mínima de mudanças na crescente peso pode ser facilmente calculado no tempo O (1) usando uma tabela de pesquisa muito pequena, como neste C do tipo 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]. Sua solução provavelmente poderia se beneficiar disso.
Fors
2

Pitão - 43 42 bytes

Utiliza uma abordagem de força bruta com todas as permutações e combinações. Não olhando mais para o golfe, porque isso se traduzirá em Pyth. Traduzido.

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

Isso é ainda mais lento que a versão python, porque eu uso filtro em vez de um loop while. Explicação em breve, agora veja o código Python.

Experimente aqui online .

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 nos pequenos, não o deixou terminar nos grandes.

Maltysen
fonte
Você não leu o código corretamente, mas você pode substituir 10 por, digamos, y5para economizar espaço em branco?
Sp3000
@ Sp3000, economizaria espaço em branco, mas não caracteres em geral. Mas eu acho que eu posso salvar um char comprimindo a lista armazenandoK=5
Maltysen
Observe que esta resposta não segue as regras como "Sua solução precisa resolver qualquer caso de teste de exemplo em menos de um minuto". (Citação está em negrito na seção de detalhes.)
randomra
0

C ++ - 863 bytes, ungolfed

Isso é executado rapidamente, no mesmo estádio da solução escrita em Rust (cerca de 6 vezes mais rápido ao compilar com a otimização ativada). Vou jogar isso mais tarde esta noite (noite na Suécia).

#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;
}
Para s
fonte