Подсчет простоватых чисел
Criado em: 18 de janeiro de 2025
Criado em: 18 de janeiro de 2025
будем называть чисоа простоватым если произведение цифр этого числа в десятичной системе счисления является простым числом. Например простоватым числом является число 21, а число 37 не является
требуется посчитать количество простоватых чисел от l до r
1 <= l <= r <= 10^100000 (не влизают в типы данных инт и лонг лонг)
реши на cpp
Анализ задачи
Нам нужно считать количество чисел на отрезке , произведение десятичных цифр которых является простым числом. Ограничение: . Очевидно, что перебор всех чисел напрямую невозможен.
Однако есть важное упрощение:
Если произведение цифр числа есть простое , тогда среди цифр этого числа может быть ровно одна цифра, равная , а все остальные цифры должны быть равны .
Обоснование:
Иными словами, «простоватое» число в десятичной записи выглядит так:
Примеры:
Как посчитать, сколько таких чисел в промежутке
Любое число длины , удовлетворя условию, задаётся выбором:
Следовательно, при фиксированной длине существует ровно подходящих чисел (4 способа выбрать «простую» цифру и способов выбрать позицию под неё).
Чтобы найти количество таких чисел , можно разделить задачу на два этапа:
Сумма для меньших длин:
Пусть = количество цифр в . Тогда числа длины от до (если ) дают
Это легко посчитать в 64-битном типе, поскольку и , что влезает в long long
.
Подсчёт среди чисел той же длины , что и :
Пусть имеет цифр: .
Рассмотрим «шаблон» = строка из единиц: "111...1"
(длиной ).
Любое «простоватое» число длины отличается от ровно в одной позиции , где ставится , а остальные — .
Чтобы определить, когда такое число лексикографически, достаточно быстро сравнивать с . Но прямой перебор чисел и сравнение каждого за даст — слишком дорого для до .
Оптимизация: сделаем всего два обхода строки :
prefixCmp: для каждой позиции храним результат сравнения префиксов длиной :
vs .
Это значение может быть , или .
По сути, идём слева направо и как только находим первую позицию, где , там определяем или и далее уже не меняем.
Псевдокод (считаем s[i] = '1'
как символ, а x[i]
— цифра-символ):
textprefixCmp[0] = EQUAL for i in [0..L-1]: if prefixCmp[i] != EQUAL: prefixCmp[i+1] = prefixCmp[i] // уже знаем меньше/больше else if '1' < x[i]: prefixCmp[i+1] = LESS else if '1' > x[i]: prefixCmp[i+1] = GREATER else prefixCmp[i+1] = EQUAL
suffixCmp: аналогично, но сравнение суффиксов vs .
Идём справа налево:
textsuffixCmp[L] = EQUAL for i in [L-1..0]: if '1' < x[i]: suffixCmp[i] = LESS else if '1' > x[i]: suffixCmp[i] = GREATER else suffixCmp[i] = suffixCmp[i+1]
Здесь тоже используем три состояния .
Теперь для конкретной позиции и выбранной «простой» цифры проверяем, будет ли число
. Делается это за :
Смотрим pc = prefixCmp[i]
— сравнение префикса длиной :
pc == LESS
, тогда . Значит весь точно (префикс уже строго меньше).pc == GREATER
, тогда независимо от того, что мы поставим в -й разряд.pc == EQUAL
, то префиксы совпадают, нужно смотреть разряд .Сравниваем и :
s[i+1..L-1]
vs x[i+1..L-1]
, то есть sc = suffixCmp[i+1]
.
sc == GREATER
, тогда , значит .Таким образом, мы за формируем массивы prefixCmp
, suffixCmp
, а потом за O(L)` (с учётом 4 простых цифр получается \(4L) считаем, сколько «просто-ватых» чисел длины не превышают .
Алгоритм целиком
Реализуем функцию
prefixCmp
и suffixCmp
за .for i in [0..L-1]
, внутри перебираем , с помощью уже описанного сравнения проверяем . Суммируем количество успехов в countLenL
.Ответ на исходный запрос :
(Причём означает, что ; если , то .)
Вся арифметика — в 64-битном типе (long long
), так как максимальное значение, которое мы можем получить, порядка , что не превосходит предел long long
.
Ниже приведён примерный код на C++ (с комментариями).
cpp#include <bits/stdc++.h> using namespace std; // Будем использовать tri-state для сравнения префиксов/суффиксов enum CompareState { LESS = -1, EQUAL = 0, GREATER = 1 }; // Вычисляет количество "простоватых" чисел, не превосходящих x (x >= 1). long long countUpTo(const string &x) { // Если x == "0" или пуста — нет подходящих чисел if (x == "0") return 0; int L = (int)x.size(); // Считаем, сколько простоватых чисел имеют длину < L: // sum_{n=1}^{L-1} (4*n) = 4 * (1 + ... + (L-1)) = 4 * (L-1)*L/2 = 2*(L-1)*L long long baseCount = 0; if (L > 1) { long long LL = L; // чтобы умножения шли в 64-битном типе baseCount = 2LL * (LL - 1LL) * LL; } // Построим prefixCmp и suffixCmp vector<CompareState> prefixCmp(L+1), suffixCmp(L+1); // prefixCmp[0] - сравнение "префикса длины 0" -> EQUAL prefixCmp[0] = EQUAL; for (int i = 0; i < L; i++) { if (prefixCmp[i] != EQUAL) { // уже знаем, что меньше или больше, состояние не меняется prefixCmp[i+1] = prefixCmp[i]; } else { // сравниваем '1' и x[i] char c = x[i]; // цифра if ('1' < c) prefixCmp[i+1] = LESS; else if ('1' > c) prefixCmp[i+1] = GREATER; else prefixCmp[i+1] = EQUAL; } } // suffixCmp[L] - сравнение "суффикса длины 0" -> EQUAL suffixCmp[L] = EQUAL; for (int i = L - 1; i >= 0; i--) { char c = x[i]; if ('1' < c) suffixCmp[i] = LESS; else if ('1' > c) suffixCmp[i] = GREATER; else suffixCmp[i] = suffixCmp[i + 1]; } // Теперь подсчитаем, сколько чисел той же длины L <= x long long countLenL = 0; static int primes[4] = {2,3,5,7}; for (int i = 0; i < L; i++) { CompareState pc = prefixCmp[i]; // сравнение префикса до i CompareState sc = suffixCmp[i+1]; // сравнение суффикса после i for (int p: primes) { // Решаем, <= x ? if (pc == LESS) { // префикс уже строго меньше, значит всё число заведомо <= x countLenL++; } else if (pc == GREATER) { // префикс строго больше, значит число > x // ничего не добавляем } else { // pc == EQUAL -> префикс совпадает, надо смотреть разряд i int digitX = x[i] - '0'; if (p < digitX) { countLenL++; } else if (p > digitX) { // y > x } else { // p == digitX -> сравниваем суффиксы // если sufixCmp == GREATER => s[i+1..] > x[i+1..], значит y>x // иначе y<=x if (sc != GREATER) { countLenL++; } } } } } return baseCount + countLenL; } // Основная функция решения int main(){ ios::sync_with_stdio(false); cin.tie(nullptr); string l, r; cin >> l >> r; // Считаем countUpTo(r) - countUpTo(l-1). // Нужно аккуратно вычислить l-1 (как строку). // Но если l="1", то l-1 = 0, просто подадим "0". // Функция для вычитания 1 из десятичной строки: auto decString = [&](string s){ // Вычитаем 1 из s (s >= "1"), результат может стать "0". int i = (int)s.size() - 1; while(i >= 0 && s[i] == '0'){ s[i] = '9'; i--; } if(i >= 0){ s[i]--; } // Удалим ведущие нули (если вдруг возникли) — // но теоретически может остаться только "0". while(s.size() > 1 && s[0] == '0') { s.erase(s.begin()); } return s; }; string lminus1; if(l == "1"){ lminus1 = "0"; // чтобы countUpTo("0") вернул 0 } else { lminus1 = decString(l); } long long ans = countUpTo(r) - countUpTo(lminus1); cout << ans << "\n"; return 0; }
Функция countUpTo(x)
:
x == "0"
, сразу возвращаем 0.L
.prefixCmp
и suffixCmp
, которые за дают нам информацию о лексикографических отношениях «строка из L
единиц» vs x
по префиксам и суффиксам.prefixCmp
/suffixCmp
решаем за , будет ли число с одной «простой» цифрой в позиции i
.Вычитание 1 из строки l
:
Нужно аккуратно обрабатывать случаи вида "1000" -> "999"
. Если равно "1"
, то (оттуда мы получаем 0 в итоге).
Итог:
Это полностью решает задачу за , что укладывается в требуемые ограничения даже при длине до цифр.