Найдите K-е наименьшее число, сумма цифр которого равна 10.

Поиск альтернативных алгоритмов

Ниже приведены те, которые я сделал, но которые были отмечены как неправильные онлайн-судьей на веб-сайте кодирования.

После объявления переменной с типом данных int k, я получил ввод из консоли с помощью cin(). Поскольку ограничения вопроса гласят, что возможное число (числа) находится в диапазоне от 1 до 20000, я сначала открыл цикл for, используя эти условия. На каждой итерации i (одна за другой) число проверяется, составляет ли сумма его цифр 10, и если да, то является ли это k-е число, сумма цифр которого равна 10.

Чтобы найти сумму цифр, я использовал либо рекурсивную функцию, либо итеративный метод с использованием цикла while. Отсюда и два фрагмента кодов. В обоих методах сумма вычисляется путем нахождения сначала цифр с использованием модульного оператора % и оператора деления /. Сумма вычисляется, а затем дополнительно проверяется, если она равна 10, и если да, она также проверяется, является ли это K-м элементом, путем подсчета всех предыдущих подобных элементов. После того, как все условия выполнены, только тогда значение i выводится с помощью cout().

#include <bits/stdc++.h>

using namespace std;

//recursion to get sum of digits.

*int sum(int d)

{

 return d==0?0:d%10+sum(d/10);

}*

int main()

{
  
  //ios_base::sync_with_stdio(false);

  //cin.tie(NULL);

   int t;

   cin>>t;

   while(t-- >0)

   {

    int k;

    cin>>k;

    for(int i=0;i<20000;i++)

    {

     int total=sum(i);

      if(total==10)

     {

      --k;

      if(k==0)

      cout<<i<<"\n";

      }

     }

  }

   return 0;

}

Во-вторых, я использовал итерации (цикл while) для вывода суммы цифр

#include <bits/stdc++.h>

using namespace std;

int main()

{
  
  //ios_base::sync_with_stdio(false);

  //cin.tie(NULL);

   int t;

   cin>>t;

   while(t-- >0)

   {

    int k;

    cin>>k;

    for(int i=0;i<20000;i++)

{

     int sum=0,d=i;

     *while(d!=0)

       {

          sum+=d%10;

           d/=10;

       }*


     if(sum==10)

       {

         --k;

          if(k==0)

          cout<<i<<"\n";

        }
    }

 }

return 0;

}

Поэтому мне нужны альтернативные алгоритмы большей эффективности. заранее спасибо


person SecondToNone    schedule 13.04.2021    source источник
comment
Вы получили неправильные ответы (для какого значения k?), или это проблема эффективности (слишком медленно)?   -  person Damien    schedule 13.04.2021
comment
Я получил правильные значения в обоих случаях реализации. Но я работаю с онлайн-судьей, этот код работает без ошибок с ограничением по времени на других компиляторах. Просто, возможно, алгоритмы, которые я использую, считаются неверными (онлайн-судья)   -  person SecondToNone    schedule 13.04.2021
comment
Обратите внимание, что вы должны выйти из цикла (или вернуться), как только получите и напечатать правильный ответ.   -  person Damien    schedule 13.04.2021
comment
Хорошо, заметил. Позвольте мне отредактировать мой код для другой ситуации, на которую может повлиять выход из цикла (возврат). Существует не только 1 тестовый пример, но и несколько тестовых случаев. Переменная для них t.   -  person SecondToNone    schedule 13.04.2021
comment
Можно ссылку на проблему.   -  person Damien    schedule 13.04.2021
comment
Вот и вся проблема теперь. Я просто пропустил тестовые случаи.   -  person SecondToNone    schedule 13.04.2021
comment
Если номер не найден, вы ничего не печатаете. Опять же, вы должны предоставить ссылку на проблему.   -  person Damien    schedule 13.04.2021
comment
mycode.prepbytes.com/contest/MARATHONAPRIL21/problems/HELPCLAY   -  person SecondToNone    schedule 13.04.2021
comment
Это ссылка на проблему.   -  person SecondToNone    schedule 13.04.2021
comment
ХОРОШО. Спасибо. Обратите внимание, что ограничение 20000 относится к вводу K, а не к числу, которое нужно найти...   -  person Damien    schedule 13.04.2021
comment
Извините, но я столкнулся с проблемой. Ваш предыдущий ответ состоит из более чем одной строки, поэтому его формат длинный и имеет (.....) точки в конце. Но я не могу больше читать после найденного слова... есть идеи, как это решить. Пытался перенести обсуждение в чат, но мне здесь не хватает репутации. Хотя мне нужна помощь. Какие-либо предложения   -  person SecondToNone    schedule 13.04.2021
comment
Но да, я заметил, что 20000 — это предел для k, а не для числа...   -  person SecondToNone    schedule 13.04.2021
comment
Спасибо за помощь, это было просто исправить благодаря вам.   -  person SecondToNone    schedule 13.04.2021
comment
Давайте продолжим это обсуждение в чате.   -  person SecondToNone    schedule 14.04.2021


Ответы (4)


Основная проблема вашего кода заключается в том, что вы ограничиваете поиск значений меньше 20000.

Для повышения эффективности я добавил два трюка

  • Я избегаю повторения одних и тех же вычислений несколько раз, запоминая предыдущие результаты.
  • По значению суммы цифр подстраиваю значение приращения

На моем ПК для вычисления максимального значения (для k = 20000) требуется 0,15 с.

Дополнительные пояснения

В коде переменная i соответствует кандидатам, то есть значениям, которые мы проверяем, равна ли сумма цифр 10 или нет.

num соответствует индексу найденных решений. Решение соответствует числу, сумма цифр которого равна 10. mem[num] = i означает, что i является num^th решением. k имеет то же значение, что и в коде OP: мы хотим найти число k^th такое, что сумма цифр = 10.

Две строки int kmax = 1; mem[1] = 19; используют тот факт, что 19 является первым допустимым решением. Это нужно для инициализации управления вектором mem, который запоминает все найденные решения.

Уловки, используемые для ускорения процесса, следующие:

  • если сумма текущего числа i равна 10, то мы знаем, что другого решения между i и i+8 нет. Например, после 27 следующее решение равно 36. Таким образом, мы можем сделать i += 9 вместо простого i++.
  • если сумма больше 10, то мы не найдем решения, просто увеличивая первую цифру. Например, если i = 85, мы можем сразу перейти к 90, т.е. обнулить наименьшую цифру. Это выполняется с помощью i += (10 - i%10);
  • если сумма меньше 10, например. 5, то вы можете напрямую добавить 5 вместо 1. Это выполняется с помощью i += (10 - total);

На практике можно было бы пойти дальше. Например, если i = 99000, то мы могли бы сразу прибавить 1000 вместо 10. Я так далеко не зашел, так как полученный код кажется уже немного надворным (0,15с вместо 1с).

#include <iostream>
#include <vector>

int sum(int d) {
    int ans = 0;
    while(d) {
        ans += d%10;
        d /= 10;
    }
    return ans;
}

int main() {
    int t;
    std::cin >> t;
    std::vector<int> mem(20001, 0);
    int kmax = 1;
    mem[1] = 19;
    while(t-- >0) {
        int i, k;
        std::cin >> k;
        int num = 1;
        if (k > kmax) {
            i = mem[kmax];
            num = kmax;
            while(true) {
                int total = sum(i);
                if(total == 10) {
                    mem[num] = i;
                    if (num == k) break;
                    num++;
                    i += 9;
                } else {
                    if (total > 10) {
                        i += (10 - i%10);
                    } else {
                        i += (10 - total);
                    }
                }
            }
            kmax = k;
        }
        std::cout << mem[k] <<"\n";
    }
    return 0;
}
person Damien    schedule 13.04.2021
comment
На моем компьютере это занимает от 3 до 5 секунд. Неоптимизированный код занимает от 8 до 10 секунд. Приятно, когда код работает быстро. - person SecondToNone; 13.04.2021
comment
Какой вариант компилятора вы использовали? Я использую O3. Вы проверили это на сайте онлайн? - person Damien; 13.04.2021
comment
Если вы считаете, что я правильно задал вопрос, пожалуйста, проголосуйте за мои ответы. Ваши ответы очень полезны. Если есть что-то еще, что вы хотели бы сообщить мне. Не стесняйтесь сказать. Я буду задавать вопросы, когда я чувствую, что не совсем понимаю. - person SecondToNone; 13.04.2021
comment
Что вы имеете в виду под моими ответами? Вы вопрос пост? Ваш ответ? - person Damien; 13.04.2021
comment
Codeblocks-офлайн. Онлайн это занимает от 2 до 4 секунд, если не используется интерактивная консоль. то есть предустановленный ввод текста. - person SecondToNone; 13.04.2021
comment
Ограничение по времени на сайте 1с. Значит, недостаточно быстро? - person Damien; 13.04.2021
comment
Мой пост с вопросами и пост с ответами (хотя ваш более эффективен). - person SecondToNone; 13.04.2021
comment
На сайте ваш работает за 0,01 с. Это достаточно быстро. - person SecondToNone; 13.04.2021
comment
Мой работает в 4-5 раз медленнее - person SecondToNone; 13.04.2021
comment
Ладно. Спасибо еще раз. - person SecondToNone; 13.04.2021
comment
Не могли бы вы объяснить, что на самом деле означают i, num и k? - person SecondToNone; 13.04.2021
comment
num соответствующий индексу найденных решений. Решение соответствует числу, сумма цифр которого равна 10. mem[num] = i означает, что i является номером^-ым решением. k имеет то же значение, что и в вашем коде: мы хотим найти k^th число такое, что сумма цифр = 10. - person Damien; 13.04.2021
comment
Теперь я понимаю ваш алгоритм, что вызывает вопрос.... Какова ситуация, когда переменная total != 10, и где мы могли бы фактически использовать код, определенный в цикле else. Насколько я понимаю, значение i с момента инициализации равно 19 или число, которое также является решением? Нам действительно нужна часть else? Искренне спрашиваешь. Поправьте меня, если мои выводы неверны. - person SecondToNone; 13.04.2021
comment
И как удаление этой части влияет на выполнение кода? - person SecondToNone; 14.04.2021
comment
Я добавил ответ с цифрой dp для обработки большего ввода. Отличный ответ, но я не понимаю основные блоки if else в цикле while(true). Не могли бы вы добавить несколько слов, чтобы описать это? - person גלעד ברקן; 14.04.2021
comment
Я попытался добавить некоторые пояснения. @גלעדברקן - person Damien; 14.04.2021
comment
Спасибо, отличные идеи! - person גלעד ברקן; 14.04.2021

Вот обязательная цифра динамической программы для полноты картины. Этот позволит вам найти 200 000-й (не говоря уже о 20 000-й) почти сразу с помощью бинарного поиска.

Код JavaScript:

function g(digits, i, sum, bound, memo){
  const key = String([i, sum, bound]);

  if (memo.hasOwnProperty(key))
    return memo[key];
    
  if (sum == 0)
    return memo[key] = 1;
    
  if (i == 0)
    return sum <= (bound ? digits[0] : 9);

  let result = 0;
  
  const k = bound ? digits[i] : 9;

  for (let d=0; d<=k && d<=sum; d++){
    const _bound = digits[i] == d ? bound : 0;
    result += g(digits, i-1, sum-d, _bound, memo);
  }
  
  return result;
}


function f(n, sum){
  const digits = [];
  
  while(n){
    digits.push(n % 10);
    n = Math.floor(n/10);
  }
  
  return g(digits, digits.length-1, sum, 1, {});
}

console.log(f(320002210, 10));

console.log(f(320002209, 10));

person גלעד ברקן    schedule 14.04.2021
comment
Выглядит вполне эффектно. Я думаю, некоторые пояснения будут полезны, по крайней мере, для таких, как я, которые не знают javascript !=) Я понимаю, что этот код быстро определяет количество решений меньше заданного числа. Затем, исходя из этого, с помощью бинарного поиска (не показано?!) можно было бы быстро определить k^th решение. Я могу ошибаться полностью... - person Damien; 14.04.2021
comment
@Damien Да, он определяет количество решений, меньшее или равное заданному числу, и двоичный поиск не включен. - person גלעד ברקן; 14.04.2021

Вы можете решить эту проблему рекурсивным способом.

Если данное число начинается с цифры d, то сумма последующих цифр должна составлять 10-d. Поскольку числа не превышают 20000, они имеют не более 5 цифр, а первая — одна из 0, 1.

Простое решение — использовать четыре вложенных цикла. Затем вы проверяете, является ли последняя цифра законной.

n= 0
for d4= 0 to 1
    for d3= 0 to min(9, d-d4)
        for d2= 0 to min(9, d-d4-d3)
            for d1= 0 to min(9, d-d4-d3-d2)
                d0= 0 d-d4-d3-d2-d1
                if 0 <= d0 and d0 <= 9:
                    n+= 1
                    if n == k stop

Возможны некоторые микрооптимизации. Используя этот метод, я нахожу 502 различных решения.

person Yves Daoust    schedule 13.04.2021
comment
Как в этом случае реализовать приведенный выше код в своем решении? - person SecondToNone; 13.04.2021
comment
@WSams; Разве псевдокод недостаточно прост? - person Yves Daoust; 13.04.2021
comment
Лемми присмотрись повнимательнее и попробуй себя еще раз. Я все еще пытаюсь понять псевдокод. - person SecondToNone; 13.04.2021
comment
Спасибо за помощь. - person SecondToNone; 13.04.2021

Я неправильно истолковал ограничения, но, наконец, вот решение. ограничение 1‹=K‹=2*10^4 было для переменной K, значение числа X было положительным целым числом, поэтому я инициализировал x=1. Я применил оператор break (чтобы вывести его из выполнения, когда число будет найдено и напечатано) внутри бесконечного цикла, то есть while (1).

Включены следующие темы: рекурсия, итерации, операторы if, операторы управления циклом.

#include <bits/stdc++.h>

using namespace std;

int main()

{
  //write your code here

  //ios_base::sync_with_stdio(false);

 // cin.tie(NULL);

  int t;

  cin>>t;

  while(t>0)

  {

    int k;

    cin>>k;

    int x=1;

    while(1)

    {

     int sum=0;

     int d=x;

     while(d!=0)

     {

     sum+=d%10;

      d/=10;

     }

      if(sum==10)

      k--;

      if(k==0)

      {

      cout<<x<<"\n";

      break;

      }

      x++;

    }

    t--;

    }

     return 0;

     }
person SecondToNone    schedule 13.04.2021
comment
Спасибо за форматирование. Полезно знать, как это делается. - person SecondToNone; 13.04.2021