Достигая счастливых чисел в репутации


21

Новый игрок в код, Джо, только что зарегистрировался на сайте. У него 1 репутация, но он намерен достичь всех своих счастливых чисел точно. Джо верит в высшие силы, которые помогут ему достичь своей цели с минимальным количеством (его или других) действий. Как новый пользователь, он также считает, что возможна негативная репутация.

Вы должны написать программу или функцию, которая поможет Джо вычислить, сколько действий он должен ожидать.

Детали

  • Действия могут изменить репутацию на следующие суммы (все действия доступны на каждом шаге независимо от правил обмена стека):

    answer accepted:     +15
    answer voted up:     +10
    question voted up:    +5
    accepts answer:       +2
    votes down an answer: −1
    question voted down:  −2
    
  • Другие особые изменения репутации не учитываются.

  • Счастливые числа могут быть отрицательными и могут быть достигнуты в любом порядке.
  • Ваше решение должно разрешить любой пример теста менее чем за минуту на моем компьютере (я буду тестировать только закрытые случаи. У меня компьютер ниже среднего.).

вход

  • Счастливые числа Джо как список целых чисел в общей форме вашего языка.

Выход

  • Количество минимальных действий, необходимых как одно целое число.
  • Вывод может быть напечатан на стандартный вывод или возвращен как целое число.

Примеры

Input => Output (пример состояния репутации)

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

Это код-гольф, поэтому выигрывает самый короткий вход.

Ответы:


1

C # - 501 байт

Обновление 551 -> 501 байт

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

Код без правил

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

16

Rust, 929 923 персонажа

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

Это было весело!


Комментарий к реализации

Так что я явно не слишком доволен размером. Но Руст абсолютно ужасен в игре в гольф в любом случае. Производительность, однако, прекрасна.

Код решает каждый из тестовых случаев правильно в почти мгновенное время, поэтому производительность, очевидно, не является проблемой. Для забавы, вот гораздо более сложный тестовый пример:

1234567 123456 12345 1234 123 777777 77777 7777 777

ответ на 82317который моя программа смогла решить на моем (средней производительности) ноутбуке за 1,66 секунды (!), даже с помощью алгоритма рекурсивного гамильтониана методом грубой силы.

наблюдения

  • Сначала мы должны построить модифицированный взвешенный граф, в котором каждый узел является «счастливым» числом, а вес - сколько изменений требуется, чтобы перейти с одного уровня репутации на другой. Каждая пара узлов должна быть соединена двумя ребрами, так как повышение значения не совпадает с понижением значения репутации (например, вы можете получить +10, но не -10).

  • Теперь нам нужно выяснить, как найти минимальное количество изменений от одного значения повторения к другому.

    • Чтобы перейти от более высокого значения к более низкому, это просто: просто возьмите, ceil((a - b) / 2)где aэто более высокое значение и bявляется более низким значением. Наш единственный логический вариант - использовать как можно больше -2, а затем - один раз, если это необходимо.

    • Значение от низкого до высокого значения немного сложнее, поскольку использование максимально возможного значения не всегда является оптимальным (например, от 0 до 9, оптимальное решение составляет +10 -1). Однако это проблема динамического программирования в учебниках, и для ее решения достаточно простого DP.

  • После того, как мы вычислили минимальные изменения от каждого числа до каждого другого числа, у нас фактически остался небольшой вариант TSP (проблема коммивояжера). К счастью, существует достаточно небольшое количество узлов (максимум 5 в самом сложном тестовом случае), для которых достаточно грубой силы для этого шага.

Код без правил (с большим количеством комментариев)

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

1
Отличный ответ! Я заинтересован в Rust, и объяснение на самом деле очень полезно для обучения. И так же, как головы, вы можете получить подсветку синтаксиса с <!-- language-all: lang-rust -->. ;)
Алексей Александрович

Я работаю над решением и увидел, что минимальное количество изменений от низкого до высокого веса можно легко вычислить в O (1) с помощью очень маленькой таблицы поиска, как в этом C-подобном псевдокоде floor((a-b)/15)+{0,2,1,2,2,1,3,2,2,2,1,3,2,2,2}[(a-b)%15]. Ваше решение, возможно, выиграет от этого.
Форс

2

Pyth - 43 42 байта

Использует полностью грубый подход со всеми перестановками и комбинациями. Не смотря на гольф больше, потому что переведу на Pyth. В переводе.

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

Это даже медленнее, чем версия Python, потому что я использую фильтр вместо цикла while. Объяснение скоро появится, теперь посмотрим на код Python.

Попробуйте здесь онлайн .

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

Работает над маленькими, не дотягивает до больших.


Не правильно прочитали код, но можете ли вы заменить 10, скажем, y5чтобы сэкономить на пустом месте?
Sp3000

@ Sp3000 это сэкономит пробел, но не любые символы в целом. Но я думаю, что могу сохранить символ, сжимая список, сохраняяK=5
Maltysen

Обратите внимание, что этот ответ не соответствует правилам, так как «Ваше решение должно решить любой пример теста за минуту». (Цитата
выделена

0

C ++ - 863 байта, без заглушки

Это выполняется довольно быстро, на том же уровне, что и решение, написанное на Rust (примерно в 6 раз быстрее при компиляции с включенной оптимизацией). Я буду играть в гольф позже этим вечером (то есть вечером в Швеции).

#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;
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.