Osiąganie szczęścia w reputacji

21

Nowy golfista, Joe, właśnie zarejestrował się na stronie. Ma 1 reputację, ale jest zdeterminowany, aby dokładnie dotrzeć do wszystkich swoich szczęśliwych liczb. Joe wierzy w wyższe moce, które pomogą mu osiągnąć swój cel przy minimalnej ilości (jego lub innych) działań. Jako nowy użytkownik uważa również, że negatywna reputacja jest możliwa.

Powinieneś napisać program lub funkcję, która pomoże Joe'emu obliczyć, ile akcji powinien się spodziewać.

Detale

  • Akcje mogą zmienić reputację o następujące kwoty (wszystkie akcje są dostępne na każdym etapie, niezależnie od zasad wymiany stosów):

    answer accepted:     +15
    answer voted up:     +10
    question voted up:    +5
    accepts answer:       +2
    votes down an answer: −1
    question voted down:  −2
    
  • Inne specjalne zmiany reputacji są pomijane.

  • Szczęśliwe liczby mogą być ujemne i można je osiągnąć w dowolnej kolejności.
  • Twoje rozwiązanie musi rozwiązać każdy przykładowy przypadek testowy w ciągu minuty na moim komputerze (przetestuję tylko zamknięte przypadki. Mam komputer poniżej średniej.)

Wkład

  • Szczęśliwe liczby Joe jako lista liczb całkowitych we wspólnej formie twojego języka.

Wydajność

  • Liczba minimalnych działań wymaganych jako jedna liczba całkowita.
  • Dane wyjściowe można wydrukować na standardowe wyjście lub zwrócić jako liczbę całkowitą.

Przykłady

Dane wejściowe => Dane wyjściowe (przykładowe stany reputacji)

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

To jest golf golfowy, więc wygrywa najkrótszy wpis.

randomra
źródło

Odpowiedzi:

1

C # - 501 bajtów

Zaktualizuj 551 -> 501 bajtów

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;}}}

Nieskluczony kod

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
źródło
16

Rdza, 929 923 znaków

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

To była zabawa!


Komentarz do wdrożenia

Więc oczywiście nie jestem zbyt zadowolony z rozmiaru. Ale Rust i tak gra w golfa. Wydajność jest jednak wspaniała.

Kod rozwiązuje każdy przypadek testowy poprawnie w niemal natychmiastowym czasie, więc wydajność nie stanowi problemu. Dla zabawy jest o wiele trudniejszy przypadek testowy:

1234567 123456 12345 1234 123 777777 77777 7777 777

na które odpowiedź brzmi 82317: który mój program był w stanie rozwiązać na moim (średnio-wydajnym) laptopie w 1,66 sekundy (!), nawet z rekurencyjnym algorytmem ścieżki hamiltonowskiej brute-force.

Spostrzeżenia

  • Najpierw powinniśmy zbudować zmodyfikowany wykres ważony, z węzłami będącymi „szczęśliwymi” liczbami, a wagami jest liczba zmian potrzebnych do przejścia z jednego poziomu reputacji na drugi. Każda para węzłów musi być połączona dwoma krawędziami, ponieważ wzrost nie jest tym samym, co spadek wartości reputacji (na przykład można uzyskać +10, ale nie -10).

  • Teraz musimy dowiedzieć się, jak znaleźć minimalną liczbę zmian między wartościami powtórzeń.

    • Aby przejść od wyższej wartości do niższej wartości, jest to proste: wystarczy wziąć ceil((a - b) / 2)gdzie ajest wyższa wartość, a bniższa wartość. Naszą jedyną logiczną opcją jest użycie -2 w jak największym stopniu, a następnie -1 w razie potrzeby.

    • Wartość od niskiej do wysokiej jest nieco bardziej skomplikowana, ponieważ użycie największej możliwej wartości nie zawsze jest optymalne (np. Dla 0 do 9 optymalne rozwiązanie wynosi +10 -1). Jest to jednak podręcznikowy problem z dynamicznym programowaniem i wystarczy prosty DP, aby go rozwiązać.

  • Po obliczeniu minimalnych zmian z każdej liczby na każdą inną liczbę, zasadniczo pozostaje nam niewielki wariant TSP (problem sprzedawcy podróży). Na szczęście istnieje wystarczająco mała liczba węzłów (maksymalnie 5 w najtrudniejszym przypadku testowym), że brutalna siła jest wystarczająca na tym etapie.

Nieskluczony kod (mocno komentowany)

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] }
}
Klamka
źródło
1
Świetna odpowiedź! Interesuję się rdzą, a wyjaśnienie jest bardzo pomocne w nauce. I tak jak w heads-upie, możesz uzyskać podświetlanie składni za pomocą <!-- language-all: lang-rust -->. ;)
Alex A.
Pracuję w roztworze i widząc, że minimalna ilość zmian w niskiej do wysokiej masie można łatwo wyliczyć w O (1), przy użyciu bardzo małej odnośników-stołu, tak jak w tym C, jak pseudo-kod floor((a-b)/15)+{0,2,1,2,2,1,3,2,2,2,1,3,2,2,2}[(a-b)%15]. Twoje rozwiązanie prawdopodobnie skorzystałoby na tym.
Fors
2

Pyth - 43 42 bajty

Wykorzystuje podejście całkowicie brutalne ze wszystkimi permutacjami i kombinacjami. Nie chcę więcej grać w golfa, bo to przełoży się na Pytha. Przetłumaczony.

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

Jest to nawet wolniejsze niż wersja Pythona, ponieważ używam filtra zamiast pętli while. Wyjaśnienie już wkrótce, teraz spójrz na kod Pythona.

Wypróbuj tutaj 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

Działa na małych, nie pozwalał na ukończenie na dużych.

Maltysen
źródło
Nie przeczytałeś poprawnie kodu, ale czy możesz zastąpić 10, powiedzmy, y5aby zapisać na białych znakach?
Sp3000 16.0415
@ Sp3000 zaoszczędziłby białych znaków, ale nie zawierałby żadnych znaków. Ale myślę, że mogę uratować znak, kompresując listę, przechowującK=5
Maltysen
Pamiętaj, że ta odpowiedź nie jest zgodna z regułami, ponieważ „Twoje rozwiązanie musi rozwiązać każdy przykładowy przypadek testowy w ciągu minuty”. (Cytat jest pogrubiony w części Szczegóły.)
randomra 16.04.15
0

C ++ - 863 bajtów, bez golfa

Działa to dość szybko, w tym samym ballparku co rozwiązanie napisane w Rust (około 6 razy szybciej przy kompilacji z włączoną optymalizacją). Będę grał w golfa później tego wieczoru (to znaczy wieczorem w Szwecji).

#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
źródło