Задача 1
Дано равенство, в котором цифры заменены на буквы: rqr + rqq = sqr. Найдите сколько у него решений, если различным буквам соответствуют различные цифры (ведущих нулей в числе не бывает).
Задача 2
Наименьшее число m, такое, что m! делится без остатка на 10 — это m=5 (5! = 120). Аналогично, наименьшее число m, такое, что m! делится без остатка на 25 — это m=10. В общем случае, значение функции s(n) равно наименьшему числу m, такому что m! без остатка делится на n.
Определим функцию S(M, N) = ∑s(n) для всех n ∈ [M, N]. К примеру, S(6, 10) = 3 + 7 + 4 + 6 + 5 = 25. Найдите S(2300000, 2400000).
Задача 3
Рассмотрим все возможные числа ab для 1<a<6 и 1<b<6:
22=4, 23=8, 24=16, 25=32 32=9, 33=27, 34=81, 35=243 42=16, 43=64, 44=256, 45=1024, 52=25, 53=125, 54=625, 55=3125. Если убрать повторения, то получим 15 различных чисел. Сколько различных чисел ab для 2<a<135 и 2<b<136?
Здравствуйте, Кодт, Вы писали:
К>На хабре статья с, как выяснилось, неудачными решениями задачек К>https://habrahabr.ru/post/311908/
К>Предлагаю размять голову самостоятельно.
К>Итак
К>Задача 1 К>Дано равенство, в котором цифры заменены на буквы: rqr + rqq = sqr. Найдите сколько у него решений, если различным буквам соответствуют различные цифры (ведущих нулей в числе не бывает).
К>>Задача 1 К>>Дано равенство, в котором цифры заменены на буквы: rqr + rqq = sqr. Найдите сколько у него решений, если различным буквам соответствуют различные цифры (ведущих нулей в числе не бывает).
S>С разбегу решилось сл. образом: S>q = 0, s = 2r S>Возможно обшибся. Остальные посмотрю потом.
Здравствуйте, Кодт, Вы писали:
К>Задача 2 К>Наименьшее число m, такое, что m! делится без остатка на 10 — это m=5 (5! = 120). Аналогично, наименьшее число m, такое, что m! делится без остатка на 25 — это m=10. В общем случае, значение функции s(n) равно наименьшему числу m, такому что m! без остатка делится на n. К>Определим функцию S(M, N) = ∑s(n) для всех n ∈ [M, N]. К примеру, S(6, 10) = 3 + 7 + 4 + 6 + 5 = 25. Найдите S(2300000, 2400000).
У меня на питоне без особых оптимизаций это заняло 9 секунд. Ответ 324780928, надеюсь, что правильный.
Здравствуйте, Кодт, Вы писали: К>У меня на питоне без особых оптимизаций это заняло 9 секунд. Ответ 324780928, надеюсь, что правильный.
Ты малость схалявил. Твой ответ верный, но для S(230000, 240000). А нужно было S(2300000, 2400000).
У меня получилось 27566064194.
Код
#include <cmath>
#include <vector>
#include <iostream>
#include <tuple>
#include <chrono>
using namespace std;
vector<int> primes({ 2, 3, 5, 7, 11, 13 });
void genPrimes(int max) {
for (int n = 15; n <= max; n += 2) {
auto it = primes.begin() + 1;
for (;;) {
if (it == primes.end()) {
primes.push_back(n);
break;
}
if (n % *it == 0) break;
++it;
}
}
}
int s(int n);
int main() {
auto t1 = chrono::high_resolution_clock::now();
constexpr int m = 2300000;
constexpr int n = 2400000;
genPrimes(static_cast<int>(sqrt(n)));
int64_t result = 0;
for (auto i = m; i <= n; ++i)
{
result += s(i);
}
cout << result << endl;
auto t2 = chrono::high_resolution_clock::now();
cout << "time: " << chrono::duration_cast<chrono::milliseconds>(t2 - t1).count() << "ms" << endl;
}
vector<tuple<int, int>> factorize(int n);
int factorial_power(int m, int p);
int s(int n)
{
auto factors = factorize(n);
int lb = 1;
for (auto v: factors)
{
int p;
int count;
tie(p, count) = v;
int lb_value = factorial_power(lb, p);
if (lb_value >= count) continue;
int ub = p * count;
if (count < p)
{
lb = ub;
continue;
}
while (lb < ub)
{
int median = lb + (ub - lb) / 2;
int m_value = factorial_power(median, p);
if (m_value < count)
{
lb = median + 1;
}
else
{
ub = median;
}
}
}
return lb;
}
int factorial_power(int m, int p)
{
int result = 0;
while (m >= p)
{
result += m / p;
m /= p;
}
return result;
}
vector<tuple<int, int>> factorize(int n) {
vector<tuple<int, int>> result;
for (auto p : primes) {
int count = 0;
while (n % p == 0)
{
++count;
n /= p;
}
if (count > 0)
{
result.push_back(make_tuple(p, count));
}
if (p > n) break;
}
if (n > 1)
{
result.push_back(make_tuple(n, 1));
}
return result;
}
Здравствуйте, Lexey, Вы писали:
L>В релизе 137 мс. И можно пооптимизировать еще.
Нужно! Двоичный поиск — дело хорошее, но он (квадратно) логарифмичен, один логарифм можно снять, если взять нижнюю границу для конкретного p равной count * (p — 1) + 1 (из суммы геометрической прогрессии), округлённому вверх до делящегося на p, а потом идти вверх шажками по p, не перевычисляя заново факториальную степень, а подсчитывая количество p в очередном множителе (в среднем это будет не более одной проверки), а шажков таких уже будет логарифмическое число. И порядком простых множителей можно поиграться, современные автоматические форы — это клёво, но если идти обратно, и есть достаточно большой простой множитель, то возможно его хватит для оставшихся маленьких (в Вашем примере — будем сразу улетать на continue), в целом это может оказаться выгодным. Ну и факторизация каждого числа — это жесть, для таких маленьких чисел тупо составляется решето на 2.4М интов, в котором каждому числу сопоставляется один из его простых множителей, тут наверное будет самый толстый выигрыш. Ну или хотя бы стандартно до корня перебирать возможные простые делители, а не до самого числа.
В общем печально всё с этим хантером, у них даже близко ничего нет к разумному решению.
Здравствуйте, cures, Вы писали:
L>>В релизе 137 мс. И можно пооптимизировать еще.
C>Нужно!
Не факт. И так очень шустро работает.
C>Двоичный поиск — дело хорошее, но он (квадратно) логарифмичен, один логарифм можно снять, если взять нижнюю границу для конкретного p равной count * (p — 1) + 1 (из суммы геометрической прогрессии), округлённому вверх до делящегося на p, а потом идти вверх шажками по p, не перевычисляя заново факториальную степень, а подсчитывая количество p в очередном множителе (в среднем это будет не более одной проверки), а шажков таких уже будет логарифмическое число.
Тут согласен.
C>И порядком простых множителей можно поиграться, современные автоматические форы — это клёво, но если идти обратно, и есть достаточно большой простой множитель, то возможно его хватит для оставшихся маленьких (в Вашем примере — будем сразу улетать на continue), в целом это может оказаться выгодным.
Об этом думал, но не стал заморачиваться.
C>Ну и факторизация каждого числа — это жесть, для таких маленьких чисел тупо составляется решето на 2.4М интов, в котором каждому числу сопоставляется один из его простых множителей, тут наверное будет самый толстый выигрыш.
Идея хорошая. Но, не принципиально, ибо факторизация все равно будет линейной (только константа уменьшается), а мороки с реализацией больше.
C>Ну или хотя бы стандартно до корня перебирать возможные простые делители, а не до самого числа.
Перебираются простые до корня из N. Корень из конкретного числа в данной задаче не сильно меньше будет.
C>В общем печально всё с этим хантером, у них даже близко ничего нет к разумному решению.
Здравствуйте, Lexey, Вы писали:
L>Не факт. И так очень шустро работает.
Вот тут чуть шустрее, хотя я всего лишь сделал перебиралку до корня в факторизации. Ну и переставил замер времени до первого вывода, ибо мерять время вывода — баловство. Если что, можно там же и меряться, мы же — профессионалы
C>>Ну и факторизация каждого числа — это жесть, для таких маленьких чисел тупо составляется решето на 2.4М интов, в котором каждому числу сопоставляется один из его простых множителей, тут наверное будет самый толстый выигрыш.
L>Идея хорошая. Но, не принципиально, ибо факторизация все равно будет линейной (только константа уменьшается), а мороки с реализацией больше.
Факторизация будет (практически) линейной по максимальному числу, а при независимой факторизации она требует примерно количество чисел в диапазоне, помноженной на корень из большего числа, то есть в худшем случае — большее число в степени 1.5.
L>Перебираются простые до корня из N. Корень из конкретного числа в данной задаче не сильно меньше будет.
Нет, перебираются до самого N. Вот если бы Вы заменили if (p > n) break; на if (p > n / p) break; то перебирались бы до корня.
Update: понял, перебираются до корня из максимального числа, но достаточно часто будут иметься маленькие множители, что сократит перебор. Можете попробовать.
Здравствуйте, cures, Вы писали:
C>Здравствуйте, Lexey, Вы писали:
L>>В релизе 137 мс. И можно пооптимизировать еще.
C>Нужно! Двоичный поиск — дело хорошее
Нуу... да, пожалуй.
Альтернатива этому — написать прямую формулу логарифма, который находил бы для очередной пары (p**count) точное значение m : m! >= p**count,
там будет что-то типа p * (какой-то ряд из int(count/p**k))
Кстати, если сплавить разложение и нахождение границы, то можно избавиться от вектора.
Просто рожаем очередную степень очередного простого числа, и тут же находим m для неё.
А в забеге находим максимум.
Здравствуйте, Кодт, Вы писали:
К>там будет что-то типа p * (какой-то ряд из int(count/p**k))
Это равно count * (p — 1), плюс совсем чуть-чуть, что за логарифмическое число шагов подбирается.
К>Кстати, если сплавить разложение и нахождение границы, то можно избавиться от вектора.
А смысл? Вектор простых делителей маленький, компилятор скорее всего соптимизирует его переприсвоения. Но сейчас нет смысла даже убирать логарифм, всё время (35ms) уходит на факторизацию, попробуйте вставить return factors.size(); в самое начала функции s после вычисления факторов. Сначала надо делать решето.
Update1: Вот накодил с решетом и ступеньками, поднял оба лимита в 10 раз, предлагаю на них и меряться, на старых слишком волатильно, а больше идеван вряд ли памяти даст.
Update2: Сделал решето двухбайтовым, перестал лишний раз считать factorial_power; чуть уменьшилось время, до 0.31 секунды. Замерил число прыжков по ступенькам (неугадывание по формуле), 17355 на миллион, это даже не логарифм, а очень хорошая константа.
Здравствуйте, Кодт, Вы писали: К>Задача 3 К>Рассмотрим все возможные числа ab для 1<a<6 и 1<b<6: К>22=4, 23=8, 24=16, 25=32 32=9, 33=27, 34=81, 35=243 42=16, 43=64, 44=256, 45=1024, 52=25, 53=125, 54=625, 55=3125. Если убрать повторения, то получим 15 различных чисел. Сколько различных чисел ab для 2<a<135 и 2<b<136?
Тут получилось 16640. Решение мне не очень нравится из-за того, что все-таки пришлось степени в set пихать в двойном цикле. Но ничего лучшего пока не придумалось.
Код
#include <cmath>
#include <vector>
#include <iostream>
#include <tuple>
#include <chrono>
#include <unordered_map>
#include <unordered_set>
#include <list>
using namespace std;
vector<int> primes({ 2, 3, 5, 7, 11 });
int solve(int minA, int maxA, int minB, int maxB);
int main() {
auto t1 = chrono::high_resolution_clock::now();
cout << solve(3, 134, 3, 135) << endl;
auto t2 = chrono::high_resolution_clock::now();
cout << "time: " << chrono::duration_cast<chrono::milliseconds>(t2 - t1).count() << "ms" << endl;
}
using factors = vector<tuple<int, int>>;
factors factorize(int n);
int h(factors const& v);
int gcd(int a, int b);
int solve(int minA, int maxA, int minB, int maxB)
{
unordered_map<int, list<factors>> m;
for (int a = minA; a <= maxA; ++a)
{
auto f = factorize(a);
auto t = h(f);
auto it = m.find(t);
if (it == m.end())
{
list<factors> v;
v.emplace_back(move(f));
m.emplace(t, move(v));
}
else
{
it->second.emplace_back(move(f));
}
}
int result = 0;
for (auto t : m)
{
auto& l = t.second;
while (!l.empty())
{
auto f1 = move(l.front());
auto it = l.erase(l.begin());
auto c1 = get<1>(f1[0]);
list<factors> similar;
while (it != l.end())
{
auto& f2 = *it;
auto c2 = get<1>(f2[0]);
auto g = gcd(c1, c2);
auto m1 = c2 / g;
auto m2 = c1 / g;
bool compatible = true;
for (size_t i = 1; i < f1.size(); ++i)
{
if (get<1>(f1[i]) * m1 != get<1>(f2[i]) * m2)
{
compatible = false;
break;
}
}
if (!compatible)
{
++it;
continue;
}
similar.emplace_back(move(f2));
it = l.erase(it);
}
if (similar.empty())
{
result += maxB - minB + 1;
continue;
}
unordered_set<int> u;
similar.emplace_front(move(f1));
for (auto& t : similar)
{
auto m = get<1>(t[0]);
for (int p = minB; p <= maxB; ++p)
{
u.insert(m * p);
}
}
result += u.size();
}
}
return result;
}
int h(factors const& v)
{
int h = 1;
for (auto t : v)
{
h *= get<0>(t);
}
return h;
}
int gcd(int a, int b)
{
for (;;)
{
int r = a % b;
if (r == 0) return b;
a = b;
b = r;
}
}
factors factorize(int n) {
vector<tuple<int, int>> result;
for (auto p : primes) {
int count = 0;
while (n % p == 0)
{
++count;
n /= p;
}
if (count > 0)
{
result.push_back(make_tuple(p, count));
}
if (p > n) break;
}
if (n > 1)
{
result.push_back(make_tuple(n, 1));
}
return result;
}
Здравствуйте, Lexey, Вы писали:
L>Тут получилось 16640. Решение мне не очень нравится из-за того, что все-таки пришлось степени в set пихать в двойном цикле. Но ничего лучшего пока не придумалось.
Выглядит немного жутковато. А почему бы не завести мапу с инта на сет интов, после разложения каждого основания a найдём его базовое основание A (разделив все показатели степеней простых факторов на их нод), и добавим в множество для A все показатели, которые вылезают из a, от g*bmin до g*bmax. После чего просто сложим мощности всех сетов. Возможно сеты даже излишни, хватит сегментов, но с сетами более общее решение.
Да, для a большего чем корень из amax, такого что A(a)=a, можно сет не заводить, оно как базовое больше никогда не всплывёт, можно сразу добавлять (bmax-bmin+1) к ответу. Итого базовых оснований в мапе будет 7 штук: [2,3,5,6,7,10,11]. Ещё до кучи можно мапу вообще не заводить, перебирать возможные базовые основания от 1 до sqrt(amax), помечать получающиеся из них степени и на ходу апдейтить соответствующий диапазон, так обходимся совсем без факторизации, но нужен битовый массивы размера (amax-amin+1) и sqrt(amax), и время работы будет пропорционально сумме их размеров, что вроде бы не хуже предлагаемого. Можно попробовать для solve(3,3000000000,4,4000000000).
К>Задача 1 К>Дано равенство, в котором цифры заменены на буквы: rqr + rqq = sqr. Найдите сколько у него решений, если различным буквам соответствуют различные цифры (ведущих нулей в числе не бывает).
Смущает, что никто не спросил уточнения про систему счисления.
К>>Задача 1 К>>Дано равенство, в котором цифры заменены на буквы: rqr + rqq = sqr. Найдите сколько у него решений, если различным буквам соответствуют различные цифры (ведущих нулей в числе не бывает).
AS>Смущает, что никто не спросил уточнения про систему счисления.
Справедливо, но лично я в этой задаче пошел кратчайшем путем. К тому же если не оговорено другого, то можно использовать "умолчание", т.е. десятичную сист. счисления.
Здравствуйте, cures, Вы писали:
C>Выглядит немного жутковато.
Да, есть такое.
C> А почему бы не завести мапу с инта на сет интов, после разложения каждого основания a найдём его базовое основание A (разделив все показатели степеней простых факторов на их нод), и добавим в множество для A все показатели, которые вылезают из a, от g*bmin до g*bmax. После чего просто сложим мощности всех сетов. Возможно сеты даже излишни, хватит сегментов, но с сетами более общее решение.
По факту, так и нужно было делать, но я сначала пытался просто дубликаты посчитать и их выкинуть из общего числа, но обломался. В итоге просто минимально переделал, чтобы работало. Целиком все перекраивать было просто лень, да и спать хотелось уже.
C>Да, для a большего чем корень из amax, такого что A(a)=a, можно сет не заводить, оно как базовое больше никогда не всплывёт, можно сразу добавлять (bmax-bmin+1) к ответу. Итого базовых оснований в мапе будет 7 штук: [2,3,5,6,7,10,11].
Согласен.
C>Ещё до кучи можно мапу вообще не заводить, перебирать возможные базовые основания от 1 до sqrt(amax), помечать получающиеся из них степени и на ходу апдейтить соответствующий диапазон, так обходимся совсем без факторизации, но нужен битовый массивы размера (amax-amin+1) и sqrt(amax), и время работы будет пропорционально сумме их размеров, что вроде бы не хуже предлагаемого. Можно попробовать для solve(3,3000000000,4,4000000000).
К>Задача 1 К>Дано равенство, в котором цифры заменены на буквы: rqr + rqq = sqr. Найдите сколько у него решений, если различным буквам соответствуют различные цифры (ведущих нулей в числе не бывает).
Один цикл от 1 до 4.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, Lexey, Вы писали:
C>>Ещё до кучи можно мапу вообще не заводить, перебирать возможные базовые основания от 1 до sqrt(amax), помечать получающиеся из них степени и на ходу апдейтить соответствующий диапазон, так обходимся совсем без факторизации, но нужен битовый массивы размера (amax-amin+1) и sqrt(amax), и время работы будет пропорционально сумме их размеров, что вроде бы не хуже предлагаемого. Можно попробовать для solve(3,3000000000,4,4000000000).
L>Вот эту идею не понял.
Перебираем базовые основания (не являющиеся степенями меньших чисел) до sqrt(amax). Например, 2 входит в отрезок [amin..amax] в степенях [2..7]. Соответствено, из списка базовых оснований вычёркиваем 4 и 8 (16 уже больше sqrt(134), а 2 ещё меньше 3), после чего для каждой из степеней двойки от второй до седьмой находим, сколько итоговых чисел она добавляет в результат. Например, 4 добавляет (bmax-bmin+1)=133 новых числа, 8 уже добавляет меньше, надо из её степеней вычесть те, которые мы уже добавили в четвёрке, 16 добавит ещё меньше, и так далее до 128. Количество добавляемых чисел придётся наверное считать по формуле включения-исключения (пересечение двух арифметических последовательностей вроде бы тоже арифметическая последовательность), поэтому к временнОй сложности добавится amax. Но таких базовых оснований, для которых будет много различных степеней, будет мало. Отдельно считаем количество чисел из отрезка [amin..amax], которые получались из наших степеней (уже сосчитаны); после пробега по базовым основаниям добавляем к итоговому результату количество непосчитанных оснований, умноженное на (bmax-bmin+1). Память нужна чтобы метить небазовые основания, битовый массив размера sqrt(amax), ну и стек размера log(amax) для рекурсивного вычисления по формуле включения-исключения.