Простоватые числа на C++

Created on: January 18, 2025

Answered using OpenAI 01 by OpenAI01

Question

реши задачу на c++. вводятся два числа l и r. Каждое из них меньше или равно 2^100000 то есть принимать их надо как строки. Надо найти количество простоватых чисел между ними включительно. Простоватым называется число произведение цифр которого простое число. То есть числа состоящие только из единицы и простой цифры

Thought

Breaking down the task

The user wants to count numbers within a given range whose product of digits is a prime number, using prime digits 2, 3, 5, and 7.

Ensuring digits align

We determine that for the product of digits to be prime, there must be exactly one prime digit (2, 3, 5, 7) among the digits, with the rest being 1's.

Identifying the criteria

We must count numbers in the range [l, r] that consist of digits '1' and exactly one prime digit from {2, 3, 5, 7}. Multiple prime digits make the product composite.

Generating and counting

I’m think through generating "простоватые" numbers by picking one prime digit from {2,3,5,7} and filling the rest with '1'. The goal is to determine their count within a range.

Understanding constraints

The input numbers, represented as strings up to 30103 digits, dictate that each string should be interpreted within these limits for accurate processing.

Ensuring comprehensive coverage

By examining all lengths up to 30103 digits and assigning a prime digit from {2, 3, 5, 7} to each position, I aim to thoroughly capture all possible valid numbers.

Counting manageable numbers

I calculated the total "простоватые" numbers to be around 1.8 billion. This volume suggests a need for a more efficient enumeration method to fit within typical time and space limits.

Calculating valid numbers

I’m focusing on defining a function to count "простоватые" numbers for each digit length up to a threshold, then summing these counts to determine the total within the desired range.

Counting and subtracting

I'm pulling together a strategy to count "простоватые" numbers by digit length up to threshold T, then subtracting the count up to T-1 to find those within the range [l, r].

Constructing and comparing

I'm detailing a method to generate "простоватые" numbers of length n, placing a prime digit in each position, comparing these numbers to T, and counting those that are less than or equal to T.

Developing comparison strategies

I’m simplifying the process by comparing digits of "простоватые" numbers in lexicographical order with T, aiming for an efficient method that avoids constructing entire numbers as strings.

Mapping out positions

I’m thinking through how to pinpoint the first differing position in T, ensuring a single comparison identifies the prime digit and confirms "A" is less than or equal to T.

Clarifying constraints

Since T cannot start with a digit less than '1', I’m working through how this confirms "A" can indeed match T up to each point, ensuring the validity of the comparison.

Improving the approach

I'm detailing a method to generate and compare "простоватые" numbers with T, iterating through prime digit positions and using a partial generation approach to ensure efficiency.

Refining comparison strategies

It's becoming clear that lexicographical comparison of digit positions can efficiently determine if "A" is less than or equal to T without constructing the entire string, ensuring more feasible operations.

Evaluating options

I’m weighing standard lexicographic comparison for matching "простоватые" numbers to T, considering efficiency, and exploring partial prefix matching to ensure an optimal solution within constraints.

Mapping the strategy

I’m thinking through a single left-to-right pass to insert prime digits in T without surpassing T, leveraging a function to identify the largest prime digit in T for efficient comparison.

Tracking progress

I’m laying out prefix arrays to monitor positions where T surpasses '1' or includes a smaller digit. This helps in counting valid "простоватые" numbers that meet the criteria.

Adjusting my strategy

I’m tweaking the plan to count valid "простоватые" numbers by examining digit positions and checking if a prime digit can be placed in A without exceeding T.

Ensuring alignment

By placing a prime digit at i and setting other positions to '1', I’m working to align A with T, taking note of smaller primes and the current digit's potential.

Ensuring consistent progress

By placing the prime digit at i and '1's elsewhere, I’m working to keep "A" aligned with "T", while being aware of any potential conflicts with smaller digits.

Ensuring alignment

I'm tackling the challenge of keeping '1's and the prime digit aligned with T. For instance, if T has a '0' in any position, "111...1" can't match T's digit.

Ensuring digit alignment

I'm focusing on keeping '1's and the prime digit aligned with T's suffix, comparing digit-by-digit to confirm that "1..1" is less than or equal to T's digits, or breaking out otherwise.

Aligning numbers

I'm piecing together the strategy to align "A" with "T" up to the prime digit and continue comparing the suffixes to establish a match. This involves a step-by-step approach.

Adjusting my approach

I’m refining the method to ensure all valid "простоватые" numbers up to T are counted, even if they surpass T's length by considering shifts and prime digit positions.

Implementing a digit dp approach

I’m piecing together a dynamic programming method that focuses on forming valid numbers by considering digit positions, the use of a prime digit, and comparisons to T's digits.

Laying out the options

I’m setting up a digit dp function to count valid "простоватые" numbers up to T, considering digit positions, prime digit use, and constraints on T's digits.

Constructing strategies

I’m narrowing down the digit dp function to manage exactly n-digit "простоватые" numbers, incorporating smaller lengths and a specific state management. This approach seems promising and logical.

Tracking transitions

I'm thinking through how to manage digit choices, prime digit usage, and constraints, ensuring the state is feasible within the problem's limits. This leads me to refine the digit dp function.

Perfecting the digit dp function

I’m refining the digit dp function to ensure it effectively handles valid "простоватые" numbers, considering digit choices, prime digit usage, and constraints to maintain feasibility within the problem's limits.

Pinpointing valid digits

I'm focusing on digits {1, 2, 3, 5, 7} for digit dp transitions, ensuring exactly one prime digit appears in the product to meet the "простоватое" condition.

Ensuring valid transitions

I’m mapping out valid digit transitions, focusing on digits {1, 2, 3, 5, 7}, and ensuring exactly one prime digit appears in the product for "простоватое" numbers.

Tracking valid sequences

I'm setting up a digit dp function to ensure sequences start with non-zero digits and maintain exactly one prime digit, using digits {1, 2, 3, 5, 7} within the constraints.

Crafting the digit dp function

I’m piecing together the digit dp function, focusing on valid digit sequences, prime digit usage, and constraints, while ensuring the states stay manageable for feasible memory usage.

Mapping out digit sequences

I'm working through how to count valid sequences with one prime digit and constraints, using top-down recursion with memoization to ensure the states stay manageable.

Ensuring valid sequences

I’m mapping out the digit dp function to count valid "простоватые" sequences, considering constraints and prime digit usage, while carefully avoiding leading zeros.

Mapping out the function

I’m detailing the digit dp function to count valid "простоватые" numbers, considering constraints, prime digit usage, and avoiding leading zeros, ensuring feasibility and clarity in the process.

Optimizing the solution

I’m focusing on the digit dp function, addressing constraints and prime digit usage, while avoiding leading zeros to ensure efficiency and avoid stack overflow.

Handling transitions

I’m mapping out the BFS approach to manage dp transitions, considering constraints and prime digit usage, and avoiding leading zeros. This approach should ensure a more efficient solution.

Tackling the problem

I’m working through the BFS approach to manage dp transitions, considering constraints and prime digit usage, and avoiding leading zeros for a more efficient solution.

Managing potential overflow

I’m verifying that results fit within the 64-bit range, ensuring big integers and large sums from dp states are correctly managed, given the problem's constraints.

Ensuring storage capacity

I’m confirming that the maximum number of n-digit "простоватые" numbers fits within a 64-bit integer, allowing for efficient iterative dynamic programming without running into issues like stack overflow or excessive memory usage.

Breaking down the method

I’m working through parsing T into Tdigit, initializing and updating dp, summing valid sequences, and addressing constraints to efficiently count valid configurations and manage memory usage.

Counting valid digits

I’m finalizing the approach to count valid "простоватые" numbers by meticulously parsing T and l, managing constraints, updating dp, and ensuring the solution efficiently handles large counts and edge cases.

Thought about простоватые числа в C++ для 1m 19s

Answer

Ниже разберём, как решить задачу пошагово, а затем приведём пример полноценной C++‑программы.


Постановка задачи

Даны два больших числа ll и rr (каждое может содержать до порядка 30 000 десятичных цифр, так как упомянуто «не превышает 21000002^{100000}»). Требуется найти количество «простоватых» чисел в диапазоне [l,r][l,\,r] включительно.

Определение «простоватого» числа.
Число называется «простоватым», если произведение его цифр есть простое число. Легко заметить, что произведение цифр будет простым тогда и только тогда, когда:

  1. Цифры числа могут быть только из набора {1,2,3,5,7}\{1,2,3,5,7\}, потому что любая другая цифра (0,4,6,8,9) сделает произведение либо нулём, либо составным числом.
  2. Среди цифр должна быть ровно одна «простая» цифра {2,3,5,7}\{2,3,5,7\}. Все остальные (если есть) – это единицы (11), которые не влияют на произведение.

Иными словами, «простоватое» число в десятичной записи – это:

  • Либо однозначное простое 2,3,5,72,3,5,7;
  • Либо любой набор из nn цифр ( n2n\ge2 ), где ровно в одной позиции стоит одна из цифр {2,3,5,7}\{2,3,5,7\}, а во всех остальных позициях стоит 11.

Примеры «простоватых» чисел:

  • 2,3,5,72, 3, 5, 7 (одна цифра);
  • 12,21,13,31,15,51,17,71,1112,1121,1211,12, 21, 13, 31, 15, 51, 17, 71, 1112, 1121, 1211, \dots и т.п.

Как посчитать количество «простоватых» чисел до заданной границы

Обозначим через countUpTo(X)\text{countUpTo}(X) количество «простоватых» чисел, не превосходящих XX. Тогда искомый результат для отрезка [l,r][l,r] равен:

countUpTo(r)    countUpTo(l1). \text{countUpTo}(r)\;-\;\text{countUpTo}(l - 1).

(Если l=1l=1, то countUpTo(0)\text{countUpTo}(0) считаем равным нулю.)

1) Подсчёт всех «простоватых» чисел меньшей разрядности

Пусть XX имеет nn цифр (в десятичной записи без ведущих нулей). Все «простоватые» числа, которые имеют строго меньше цифр (то есть от 1 до n1n-1 цифр), можно посчитать очень просто:

  • Для каждого количества цифр kk (1kn11 \le k \le n-1) «простоватых» чисел ровно 4×k4 \times k.
    Объяснение:
    • Нужно выбрать единственную «простую» цифру {2,3,5,7}\{2,3,5,7\} (всего 4 варианта).
    • Нужно выбрать позицию для этой цифры среди kk позиций (всего kk вариантов).
    • Остальные k1k-1 позиций будут заполнены единицами.

Следовательно, если n2n \ge 2,

CountSmallerLengths(n)  =  k=1n1(4k)  =  4(n1)n2  =  2n(n1). \text{CountSmallerLengths}(n) \;=\; \sum_{k=1}^{\,n-1} (4 \cdot k) \;=\; 4 \cdot \frac{(n-1)\,n}{2} \;=\; 2\,n\,(n-1).

(При n=1n=1 такой суммы нет, потому что «меньше 1 цифры» не бывает.)

2) Подсчёт «простоватых» чисел ровно nn‑значных, которые X\le X

Теперь надо посчитать, сколько nn-значных «простоватых» чисел не превосходит XX (где XX имеет nn цифр). Очевидно, nn-значное число не может начинаться с нуля, но его цифры могут быть лишь из множества {1,2,3,5,7}\{1,2,3,5,7\}. Причём ровно одна из них должна быть из {2,3,5,7}\{2,3,5,7\}, а остальные – 11.

Однако просто перебирать все 4n4 \cdot n кандидатов для nn‑значного числа и сравнивать с XX можно, когда nn невелико. Но nn здесь может достигать десятков тысяч, что слишком большое для «в лоб» генерации и посимвольных сравнений O(n2)O(n^2).

Решение через «цифровую динамику» (Digit DP)

Упростим себе жизнь, заметив, что:

  • Мы не рассматриваем никакие цифры, кроме {1,2,3,5,7}\{1,2,3,5,7\}.
  • У нас есть ограничение сверху XX, которое мы хотим не превышать.
  • Нужно учесть условие «ровно одна простая цифра».

Такую задачу удобно решать стандартной техникой «цифровой динамики» (digit DP).

  1. Пусть XX имеет nn цифр: x0x1xn1x_0 x_1 \dots x_{n-1} ( x00x_0 \neq 0 ).

  2. Определим массив dp[pos][usedPrime][isTight], где:

    • pos — текущая позиция (от 0 до nn) при построении числа слева направо;
    • usedPrime{0,1,2}\{0,1,2\} — сколько раз мы уже использовали «простую» цифру (0 или 1; 2 будет означать «неподходящее» состояние);
    • isTight{0,1}\{0,1\} — признак того, ограничены ли мы текущей цифрой xposx_{pos}.
      Если isTight = 1, мы не можем выбирать цифру больше, чем xposx_{pos}. Если isTight = 0, можем выбирать любую разрешённую цифру (но мы и так хотим только из {1,2,3,5,7}\{1,2,3,5,7\}).
  3. Переходы таковы:

    • На шаге pos смотрим, какую цифру d{1,2,3,5,7}d \in \{1,2,3,5,7\} мы можем поставить:
      • Она не должна быть больше xposx_{pos}, если isTight=1.
      • Если d{2,3,5,7}d\in\{2,3,5,7\}, то мы увеличиваем счётчик usedPrime. Если он становится 2 — значит, это уже вторая простая цифра, и такое число не подходит (произведение цифр перестаёт быть простым). То есть такие варианты мы в dp не добавляем.
    • Вычисляем nextIsTight. Он будет равен 1, если до этого мы шли «впритык» (isTight=1) и сейчас d=xposd = x_{pos}. Иначе nextIsTight=0.
    • Переходим к состоянию dp[pos+1][ nextUsedPrime ][ nextIsTight ], добавляя количество вариантов.
  4. Начальные условия:

    • dp[0][0][1] = 1 (ещё не выбрали ни одной цифры, не использовали простую, и пока полностью «прижаты» к верхней границе, ведь мы ничего не поставили).
    • Все остальные dp[...] = 0 в начале.
  5. Когда pos = n, мы заканчиваем построение nn-значного числа. Условие «ровно одна простая цифра» эквивалентно usedPrime=1.
    Суммарное число подходящих nn-значных вариантов (не превышающих XX) равно

    dp[n][1][0]  +  dp[n][1][1]. dp[n][1][0] \;+\; dp[n][1][1].
  6. Итого, общее количество для длины nn есть результат этой цифровой динамики.

Формула для countUpTo(X)\text{countUpTo}(X)

  1. Пусть n=n = число цифр в XX. Если XX = "0" (или меньше "1"), то понятно, что «простоватых» чисел 0\le 0 нет. Возвращаем 0.
  2. Считаем CountSmallerLengths=k=1n14k  =  2n(n1)(если n2, иначе 0). \text{CountSmallerLengths} = \sum_{k=1}^{n-1} 4k \;=\; 2 \, n \, (n-1)\quad (\text{если }n\ge 2,\text{ иначе }0).
  3. С помощью описанной digit‑DP находим countNDigits\text{countNDigits} — сколько nn‑значных «простоватых» чисел X\le X.
  4. Тогда countUpTo(X)  =  CountSmallerLengths  +  countNDigits. \text{countUpTo}(X) \;=\; \text{CountSmallerLengths} \;+\; \text{countNDigits}.

После этого для исходных l,rl,r ответ будет

countUpTo(r)    countUpTo(l1). \text{countUpTo}(r)\;-\;\text{countUpTo}(l-1).

Полный пример решения на C++

Ниже приведён упрощённый вариант кода на C++. Основная идея — реализовать функцию countUpTo(const string &X), которая возвращает количество «простоватых» чисел X\le X, и затем вернуть разницу для [l,r][l,r].

Обратите внимание:

  • Функция для вычитания 1 из строки l (когда считаем l1l-1) должна аккуратно обрабатывать случай l = "1" → тогда получим "0".
  • Мы используем 64-битный тип (long long или int64_t) для счётчиков. Максимально возможное значение при n30000n\approx 30000 — это порядка 1.8×1091.8 \times 10^9, что укладывается в диапазон 64-бит.
  • Для ускорения (и чтобы не «держать» массив dp в три измерения) можно делать «прокат» (rolling array) по pos — т.е. держать dp только для текущего и следующего pos. Но и статический массив размером [30001][2][2] по памяти тоже вполне возможен (это около 120 000 ячеек, что не слишком много).
#include <bits/stdc++.h> using namespace std; // Удобное множество "разрешённых" цифр (кроме '0', т.к. хотим именно n-значные без лидирующих нулей). static const vector<int> ALLOWED = {1,2,3,5,7}; // Вычитаем 1 из строкового числа (предполагается, что оно > "0"). string decString(const string &s) { // Если s == "0", вернём "0", но будем вызывать осознанно только для s >= "1". // Если s == "1", то s-1 = "0". // Иначе обычное вычитание 1 в десятичном виде. if (s == "0") return "0"; if (s == "1") return "0"; // Алгоритм вычитания: // идём с конца, вычитаем 1, если получается отрицательно, занимаем у предыдущей цифры и т.д. string result = s; int i = (int)result.size() - 1; while (i >= 0) { if (result[i] == '0') { result[i] = '9'; i--; } else { result[i]--; break; } } // Удалим возможные ведущие нули (но в принципе их быть не должно, кроме случая "10"-1="09"). while (result.size() > 1 && result[0] == '0') { result.erase(result.begin()); } return result; } // Подсчитываем, сколько &laquo;простоватых&raquo; чисел <= X. long long countUpTo(const string &X) { // Если X <= 0, то ответ 0. // (Например, X == "0" или пустая — никаких положительных чисел <= 0 нет.) if (X == "0") { return 0; } // Узнаем длину X. int n = (int)X.size(); // 1) Числа меньшей разрядности: long long smallerLengths = 0; if (n > 1) { // sum_{k=1..n-1} (4*k) = 4 * (1+2+...+(n-1)) = 4*(n-1)*n/2 = 2*n*(n-1). smallerLengths = 2LL * n * (n - 1); } // 2) Посчитаем кол-во n-значных &laquo;простоватых&raquo; чисел, не превышающих X, через digit DP. // Сначала переведём X в массив цифр (int). vector<int> digits(n); for (int i = 0; i < n; i++) { digits[i] = X[i] - '0'; } // dp[pos][usedPrime][isTight]: // pos -- индекс, который заполняем (0..n) // usedPrime -- сколько раз мы уже использовали "простую" цифру (0 или 1; если станет 2, недопустимо) // isTight -- флаг (0 или 1), показывающий, ограничены ли мы сверху digits[pos]. static long long dp[30002][2][2]; memset(dp, 0, sizeof(dp)); // Начальное состояние: ещё не поставили ни одной цифры, // usedPrime=0, isTight=1 (т.к. пока не выбрали ничего и строго ограничены X). dp[0][0][1] = 1; for (int pos = 0; pos < n; pos++) { for (int usedPrime = 0; usedPrime < 2; usedPrime++) { for (int tight = 0; tight < 2; tight++) { long long ways = dp[pos][usedPrime][tight]; if (ways == 0) continue; int limit = (tight ? digits[pos] : 9); // Перебираем разрешённые цифры d из {1,2,3,5,7}, которые <= limit for (int d : ALLOWED) { if (d > limit) break; // так как ALLOWED отсортирован int newUsedPrime = usedPrime + ((d == 1) ? 0 : 1); if (newUsedPrime > 1) { // означает, что получилось >= 2 простых цифр => произведение не простое continue; } int newTight = tight && (d == limit); dp[pos+1][ newUsedPrime ][ newTight ] += ways; } } } } // Кол-во n-значных &laquo;простоватых&raquo; чисел <= X: long long countNdigits = dp[n][1][0] + dp[n][1][1]; // Итог: return smallerLengths + countNdigits; } int main(){ ios::sync_with_stdio(false); cin.tie(nullptr); string l, r; cin >> l >> r; // Функция для преобразования l-1: auto lMinus1 = decString(l); long long ans = countUpTo(r) - countUpTo(lMinus1); cout << ans << "\n"; return 0; }

Пояснения к коду

  1. decString(...)
    Умеет аккуратно вычитать 1 из десятичной строки.

    • Если вход равен "1", результатом будет "0".
    • Если было что-то побольше, идёт стандартное «занимание» при вычитании.
  2. countUpTo(X)

    • Если X="0"X = "0", сразу возвращаем 0, т.к. нет положительных чисел 0\le 0.
    • Считаем n=X.size()n = X.size().
    • Добавляем в результат количество «простоватых» чисел меньших разрядностей (если n>1n>1). Формула 4+8++4(n1)=4(n1)n2=2n(n1)4 + 8 + \dots + 4(n-1) = 4\cdot\frac{(n-1)n}{2} = 2n(n-1).
    • Затем запускаем «цифровую динамику» (digit DP), которая перебирает именно nn-значные варианты (за счёт того, что первая цифра не может быть 0, а мы и не позволяем в цикле). Из множества {1,2,3,5,7} берём только те, которые \le соответствующей цифре XX (если флаг isTight = 1).
    • В конце суммируем dp[n][1][0]\text{dp}[n][1][0] и dp[n][1][1]\text{dp}[n][1][1], то есть все способы закончить ровно с одной «простой» цифрой и при этом число не превысило XX.
  3. Основная программа

    • Считывает строки ll и rr.
    • Считает countUpTo(r)countUpTo(l1)\text{countUpTo}(r) - \text{countUpTo}(l-1).
    • Печатает результат.

Оценка сложности

  • Длина входных чисел может доходить до ~30 000 цифр.
  • В нашем DP-массиве dp[pos][usedPrime][isTight]\text{dp}[pos][usedPrime][isTight] будет примерно n×2×2=4nn \times 2 \times 2 = 4n состояний. Для n=30000n=30\,000 это 120 000 состояний.
  • Из каждого состояния делаем перебор по 5 «разрешённым» цифрам ({1,2,3,5,7}\{1,2,3,5,7\}). Получаем порядка 600000600\,000 операций, что для C++ вполне выполнимо.
  • Память под DP в статическом массиве: 30001×2×2120000\sim 30001 \times 2 \times 2 \approx 120000 ячеек long long. Это менее 1 МБ.
  • Все подсчёты укладываются в 64‑бит (с запасом).

Таким образом, решение работает достаточно быстро даже при nn до 30 000.


Итого

  1. Читаем ll и rr как строки.
  2. Считаем результат как countUpTo(r)countUpTo(l1)\text{countUpTo}(r) - \text{countUpTo}(l - 1).
  3. «Простоватые» числа — те, что содержат ровно одну простую цифру (2,3,5,72,3,5,7), а остальные 11.
  4. Подсчёт выполняем через сумму по меньшим разрядностям + «цифровую динамику» для длины, равной длине текущего XX.

В коде выше показан полный рабочий пример на C++. Он и решает задачу.

Share this Q&A