Подсчет простоватых чисел

Criado em: 18 de janeiro de 2025

Respondido usando OpenAI 01 por OpenAI01

Pergunta

будем называть чисоа простоватым если произведение цифр этого числа в десятичной системе счисления является простым числом. Например простоватым числом является число 21, а число 37 не является
требуется посчитать количество простоватых чисел от l до r
1 <= l <= r <= 10^100000 (не влизают в типы данных инт и лонг лонг)
реши на cpp

Pensamento

Mapping the problem

I’m investigating numbers whose digits multiply to a prime (категория "просторатым"). This leads me to think straightforwardly without intricate operations.

Evaluating digit DP

I'm piecing together digit DP to count numbers with prime digit products. Given the challenge of large products, another method might be needed for feasibility.

Focusing on digit structures

OK, let me see. If a number's digit product is prime, then it must only include the prime itself or 1. This narrows down the valid digit configurations significantly.

Identifying the pattern

I’m realizing that to get a prime product, the number must have just one prime digit (2, 3, 5, or 7) and the rest should be 1.

Identifying key digits

A "просторатым" number has one of 2, 3, 5, or 7, with the rest being 1s. Digits like 0, 4, 6, 8, and 9 are excluded.

Calculating valid numbers

For each digit length (n), I consider 4 choices for the prime digit and n placements, ensuring no leading zeros are included.

Filtering valid numbers

I’m thinking about generating all valid numbers and filtering those within the interval [l, r]. Ensuring the prime digit is not the leading digit will help narrow down the valid numbers.

Mapping the solution

Generating up to 400,000 numbers with up to 100,000 digits is impractical. Opting for a compressed representation or counting method seems more feasible.

Ping the count

Unraveling the function f(x) to tally "просторатым" numbers from 1 to x, I plan to count those in a specific range [l, r].

Breaking down the count

I’m thinking about the count of "просторатым" numbers by combining those with lengths less than L and those less than or equal to x, ensuring the total fits within a 64-bit integer.

Mapping the strategy

Mapping out the approach, I’m considering a method similar to lexicographical order to count "просторатым" numbers up to x, aiming to efficiently handle large values.

Spotting prime digit placements

Considering a single pass approach to place prime digits, ensuring the number formed is less than or equal to x, to efficiently count potential "просторатым" numbers.

Laying out options

I’m hashing out a lexicographical comparison of numbers formed by placing prime digits, aiming for a more efficient approach to counting "просторатым" numbers.

Piecing together

I’m crafting a strategy to compare numbers by setting up a 'prefix compare' array. It stores if a number is less, equal, or greater than x up to a certain position.

Assessing digit impact

I’m working through how each digit of x might affect the comparison with the all-1s sequence, considering the first position where x's digit diverges from 1.

Formulating the approach

I’m working out arrays for partial sums to compare strings and ensure constant time checks for each digit placement in x's and y's comparison.

Mapping out the strategy

I’m planning to compare y to x by examining digit alignments, ensuring each position adheres to the rules for maintaining a constant time check. Progress is steady and intriguing.

Refining the strategy

I’m noting the potential of combining prefix and suffix comparisons to streamline the process, aiming for a more efficient solution by combining elements of both methods.

Mapping out comparisons

I’m integrating prefixCmp and suffixCmp arrays to effectively compare numbers, ensuring a constant time check for each digit placement in the verification process.

Charting the path

I’m combining prefixCmp and suffixCmp arrays to efficiently check each digit placement in y against x, ensuring y is less than or equal to x. This method streamlines the verification process.

Formulating the strategy

I’m detailing how to compute prefixCmp and suffixCmp arrays to compare y and x, ensuring constant time checks for each digit placement, aiming for an efficient comparison method.

Validating comparisons

I’m refining the check(i, p) function to ensure each digit placement in y against x is consistently evaluated, considering suffixes and prefixes for a thorough check.

Counting valid numbers

I’m thinking through counting valid numbers by summing valid pairs (i, p) and adding this to the count of "просторатым" numbers with lengths less than L.

Detailing the strategy

I’m Google Design detailing the counting process for "просторатым" numbers, addressing edge cases, digit placement verification, and efficient comparison methods for large values and ranges.

Charting digit placements

I’m mapping out the digit placement verification process, detailing how each digit of y is checked against x using prefix and suffix comparison arrays to ensure accuracy.

Ensuring clarity

I’m working through edge cases like x=1 or x=2, ensuring our approach remains efficient, and verifying that no leading zeros impede progress.

Implementing strategies

I’m detailing the implementation of functions and arrays to efficiently count valid numbers, considering prime digit placements and edge cases throughout the process.

Ensuring accuracy

I’m piecing together the suffixCmp logic to gauge digit alignment and wrapping up the valid count tally with a special check for zero, ensuring comprehensive validation throughout.

Assembling the strategy

I’m aggregating valid counts for various digit lengths by combining prefixCmp, suffixCmp arrays, and a summation formula, noting edge cases and single-digit x handling.

Thought about простоватые числа для 58 секунд

Resposta

Анализ задачи

Нам нужно считать количество чисел на отрезке [l,r][l, r], произведение десятичных цифр которых является простым числом. Ограничение: 1lr101000001 \le l \le r \le 10^{100000}. Очевидно, что перебор всех чисел напрямую невозможен.

Однако есть важное упрощение:

Если произведение цифр числа есть простое pp, тогда среди цифр этого числа может быть ровно одна цифра, равная p{2,3,5,7}p\in\{2,3,5,7\}, а все остальные цифры должны быть равны 11.

Обоснование:

  • Цифры 0,4,6,8,9 (а также наличие сразу двух простых цифр) делают произведение либо нулём, либо составным числом (больше не может быть простым).
  • Цифра 1 не меняет произведение (она «нейтральна»).

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

  • Оно содержит ровно одну цифру из множества {2,3,5,7}\{2,3,5,7\}.
  • Все остальные его цифры равны 1.
  • Цифр 0,4,6,8,9 оно не содержит вовсе.

Примеры:

  • 2121 — «простоватое» (цифры 22 и 11, произведение 21=22\cdot1=2, простое).
  • 3737 — не подходит (цифры 33 и 77, произведение 2121, составное).

Как посчитать, сколько таких чисел в промежутке [l,r][l, r]

  1. Любое число длины nn, удовлетворя условию, задаётся выбором:

    • Одной позиции ii (0i<n)(0 \le i < n), где стоит одна из цифр {2,3,5,7}\{2,3,5,7\}.
    • Остальные n1n-1 позиций заполнены цифрой 11.

    Следовательно, при фиксированной длине nn существует ровно 4×n4 \times n подходящих чисел (4 способа выбрать «простую» цифру и nn способов выбрать позицию под неё).

  2. Чтобы найти количество таких чисел x\le x, можно разделить задачу на два этапа:

    • Посчитать, сколько «простоватых» чисел меньшей длины, чем длина xx.
    • Плюс сколько «простоватых» чисел той же длины, что и xx, при этом x\le x лексикографически.
  3. Сумма для меньших длин:

    Пусть LL = количество цифр в xx. Тогда числа длины от 11 до L1L-1 (если L>1L>1) дают

    n=1L1(4n)  =  4(L1)L2  =  2L(L1). \sum_{n=1}^{L-1} (4n)\;=\;4\,\frac{(L-1)L}{2}\;=\;2\,L\,(L-1).

    Это легко посчитать в 64-битном типе, поскольку L100000L \le 100000 и 2×100000×999992×10102 \times 100000 \times 99999 \approx 2\times10^{10}, что влезает в long long.

  4. Подсчёт среди чисел той же длины LL, что и xx:

    Пусть xx имеет LL цифр: x[0],x[1],,x[L1]x[0],x[1],\dots,x[L-1].
    Рассмотрим «шаблон» ss = строка из LL единиц: "111...1" (длиной LL).
    Любое «простоватое» число длины LL отличается от ss ровно в одной позиции ii, где ставится {2,3,5,7}\{2,3,5,7\}, а остальные — 11.

    Чтобы определить, когда такое число x\le x лексикографически, достаточно быстро сравнивать с xx. Но прямой перебор L4L\cdot 4 чисел и сравнение каждого за O(L)O(L) даст O(L2)O(L^2) — слишком дорого для LL до 100000100000.

    Оптимизация: сделаем всего два обхода строки xx:

    • prefixCmp: для каждой позиции ii храним результат сравнения префиксов длиной ii:
        s[0..i1]\;s[0..i-1] vs   x[0..i1]\;x[0..i-1].
      Это значение может быть LESS=1\mathit{LESS} = -1, EQUAL=0\mathit{EQUAL} = 0 или GREATER=+1\mathit{GREATER} = +1.
      По сути, идём слева направо и как только находим первую позицию, где s[j]x[j]s[j]\neq x[j], там определяем LESS\mathit{LESS} или GREATER\mathit{GREATER} и далее уже не меняем.

      Псевдокод (считаем s[i] = '1' как символ, а x[i] — цифра-символ):

      prefixCmp[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: аналогично, но сравнение суффиксов   s[i..L1]\;s[i..L-1] vs   x[i..L1]\;x[i..L-1].
      Идём справа налево:

      suffixCmp[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]
      

      Здесь тоже используем три состояния {1,0,+1}\{-1,0,+1\}.

    Теперь для конкретной позиции ii и выбранной «простой» цифры p{2,3,5,7}p \in \{2,3,5,7\} проверяем, будет ли число

    y=1111до ip1111после i y = \underbrace{111\ldots1}_{\text{до }i}\,p\,\underbrace{111\ldots1}_{\text{после }i}

    x\le x. Делается это за O(1)O(1):

    1. Смотрим pc = prefixCmp[i] — сравнение префикса длиной ii:

      • Если pc == LESS, тогда s[0..i1]<x[0..i1]s[0..i-1] < x[0..i-1]. Значит весь yy точно x\le x (префикс уже строго меньше).
      • Если pc == GREATER, тогда y>xy > x независимо от того, что мы поставим в ii-й разряд.
      • Если pc == EQUAL, то префиксы совпадают, нужно смотреть разряд ii.
    2. Сравниваем pp и x[i]x[i]:

      • Если p<x[i]p < x[i], значит y<xy < x.
      • Если p>x[i]p > x[i], значит y>xy > x.
      • Если p=x[i]p = x[i], то нужно ещё посмотреть суффикс s[i+1..L-1] vs x[i+1..L-1], то есть sc = suffixCmp[i+1].
        • Если sc == GREATER, тогда s[i+1..]>x[i+1..]s[i+1..] > x[i+1..], значит y>xy>x.
        • Иначе yxy\le x.

Таким образом, мы за O(L)O(L) формируем массивы prefixCmp, suffixCmp, а потом за O(L)` (с учётом 4 простых цифр получается \(4L) считаем, сколько «просто-ватых» чисел длины LL не превышают xx.

Алгоритм целиком

  1. Реализуем функцию

    \text{countUpTo}(x) = \#\{\text{&laquo;простоватые&raquo; } \le x\}.
    • Если x=0x = 0 (или строка "0"), возвращаем 0. (В задаче l1l\ge1, но иногда удобно подстраховаться.)
    • Пусть L=длина(x)L = \text{длина}(x).
    • Вычисляем baseCount=n=1L14n=2(L1)L \text{baseCount} = \sum_{n=1}^{L-1} 4n = 2\,(L-1)\,L (если L>1L>1, иначе 0).
    • Строим prefixCmp и suffixCmp за O(L)O(L).
    • Делаем цикл for i in [0..L-1], внутри перебираем p{2,3,5,7}p \in \{2,3,5,7\}, с помощью уже описанного сравнения проверяем x\le x. Суммируем количество успехов в countLenL.
    • Итог: return=baseCount+countLenL\text{return} = \text{baseCount} + \text{countLenL}.
  2. Ответ на исходный запрос   [l,r]\;[l,r]:

    \#\{\text{&laquo;простоватые&raquo; } \in [l,r]\} \;=\; \text{countUpTo}(r)\;-\;\text{countUpTo}(l-1).

    (Причём l>1l>1 означает, что l11l-1 \ge1; если l=1l=1, то countUpTo(0)=0\text{countUpTo}(0)=0.)

  3. Вся арифметика — в 64-битном типе (long long), так как максимальное значение, которое мы можем получить, порядка 210102\cdot 10^{10}, что не превосходит предел long long.

Ниже приведён примерный код на C++ (с комментариями).

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

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

  1. Функция countUpTo(x):

    • Если входная строка x == "0", сразу возвращаем 0.
    • Сохраняем length(x)\text{length}(x) в L.
    • Считаем вклад от всех чисел меньших длины: n=1L14n=2L(L1)\sum_{n=1}^{L-1} 4n = 2\,L\,(L-1).
    • Строим массивы prefixCmp и suffixCmp, которые за O(L)O(L) дают нам информацию о лексикографических отношениях «строка из L единиц» vs x по префиксам и суффиксам.
    • Перебираем позиции i=0..L1i=0..L-1 и простые цифры p{2,3,5,7}p\in\{2,3,5,7\}. По готовой информации из prefixCmp/suffixCmp решаем за O(1)O(1), будет ли число с одной «простой» цифрой в позиции i x\le x.
    • Суммируем такие случаи.
  2. Вычитание 1 из строки l:
    Нужно аккуратно обрабатывать случаи вида "1000" -> "999". Если ll равно "1", то l1="0"l-1 = "0" (оттуда мы получаем 0 в итоге).

  3. Итог:

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

Это полностью решает задачу за O(r+l)O(|r| + |l|), что укладывается в требуемые ограничения даже при длине до 10510^5 цифр.

Compartilhar esta Q&A