Pull to refresh

Comments 6

Задача решается довольно простым динамическим программированием: F(k,r) - сколько вариантов взять какие-то из первых k чисел, сконкатенировать их и получить число с остатком r.

Пересчет: F(k,r) = F(k-1,r) + F(k-1, r'), где r' такое, что если приписать k-ое число к остатку r', то получится остаток r. При приписывании мы берем каждую цифру числа со старших, умножаем остаток на 10, прибавляем цифру и берем по модулю 13. Удобно это реализовывать снизу вверх итеративно.

Ответ будет в F(n, 0). База: F(0,0) = 1, F(0, r) = 0.

Все решение будет за O(N), Где N - общаяя длина всех чисел. Для произвольного делителя R это будет O(NR). Памяти тут требуется O(R).

Основная идея: если вы к числу припишите цифру c, то остаток от деления на 13 станет (10*r+c) mod 13. Поэтому вам не надо следить за тем, какое у вас конкретно сейчас число сконкатенировано, вам важен лишь его остаток от деления на 13.

Удобно это все реализовать в итеративно:

код
int Count(vector<string> numbers) {
  int dp[13] = {0};
  dp[0] = 1;
  for (string x: numbers) {
    int next[13] = {0};
    for (int i = 0; i < 13; ++i) {
      int r = i;
      for (char c : x) {
        r = (10*r + c - '0') % 13;
      }
      next[r] = dp[i];
    }
    for (int i = 0; i < 13; ++i) {
      dp[i] += next[i];
    }
  }
  return dp[0];
}

Да, всё так :) хотя я сам не отдельно по цифрам "приписывал". Думаю, впрочем, среднестатистический программист не слишком знакомый с ДП не много что из этого описания поймёт. И хорошо - всё же это больше на "подумать самому" :)

Не могу сказать, что это решение оптимально: тут мы 13 раз делаем забег по одному числу!

Лучше уж поступить следующим образом.

Каждое число является линейной функцией для любых предыдущих: была цепочка L, пририсовали цепочку M, получили N = L * 10^w + M, где w - длина M.

А теперь то же самое по модулю: n = l * f + r

int dp[13] = {1 /* , 0... */};
for (const string& x : numbers) {
  // один раз вычисляем наш линейный оператор
  int f = 1, r = 0;
  for (char c : x) {
    f = f * 10 % 13;
    r = (r * 10 + c - '0') % 13
  }
  // затем применяем его к предыдущему слою данных
  int next[13] = {/* 0... */};
  for (int l = 0; l < 13; ++l) {
    int n = (l * f + r) % 13;
    next[n] += dp[l];
  }
  // и порождаем новый слой
  for (int n = 0; n < 13; ++n) {
    dp[n] = (dp[n] + next[n]) % 1000000007;
  }
}
return dp[0];

Да, это хорошая оптимизация.

Я дуже балован сайтом leetcode на уровне hard, поэтому эта задачка показалась лёгкой.

Разве что сама формулировка изящная и забавная, то есть, это больше про искусство задавать задачки, чем про решать их.

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

Sign up to leave a comment.