Слайд 1#2. Оптимизация
Чайка К.В.
Разбор задач на C++
Слайд 21. Убрать лишние итерации
Тест простоты
Натуральное число N называется простым, если оно
делится без остатка только на себя и единицу. Определить для заданного натурального N, является ли оно простым. Лимит времени 0.1 секунда.
Входные данные
Одно целое 0 < N ≤ 4000000000.
Выходные данные
Одно слово YES (без перевода строки), если N простое, либо слово NO, если N – составное.
Пример
Ввод Вывод
1223 YES
5000001 NO
Слайд 32. Тест простоты – решение #1
#include
int main() {
unsigned
int n;
bool isprime = true;
std::cin >> n;
for(unsigned int i = 2; i < n; i++)
if(n % i == 0)
isprime = false; // число составное
std::cout << (isprime ? "YES" : "NO");
}
Слайд 43. Тест простоты – решение #1
Улучшить решение задачи можно, если искать
делители не в диапазоне до N-1, а до корня из N. В этом легко убедиться. Если у N есть делитель m>√N то N представимо в виде m×k, причем для k обязательно k<√N
Иначе получаем противоречие
#include
int main() {
unsigned int n;
bool isprime = true;
std::cin >> n;
for(int i = 2; i * i <= n; i++)
if(n % i ==0 ) isprime = false;
std::cout << (isprime ? "YES" : "NO");
}
Слайд 54. Тест простоты – решение #3
#include
int main(){
unsigned int n;
std::cin
>> n;
bool isprime = n%2 && n>2; //простое нечетно
for(int i=3; isprime && i*i<=n; i+=2)
isprime=n % i;
std::cout << (isprime ? "YES" : "NO");
}
Слайд 65. Убрать лишние циклы - прогрессии
Замок (acmp.ru)
Замок состоит из K уровней.
Каждый уровень - это правильный N-угольник, угол которого совпадает с углом предыдущего. На сторонах первого уровня находится по две комнаты (в углах), на сторонах каждого следующего - на одну больше. Сколько в замке комнат?
Входные данные
Два целых числа N и K (3 ≤ N ≤ 106; 1 ≤ K ≤ 106).
Выходные данные
Выведите целое число - количество комнат в замке.
Пример
INPUT.TXT OUTPUT.TXT
6 3 28
Слайд 76. Замок – решение #1
Рассмотрим «уровни» N-угольников. Начальным уровнем является комната,
от которой они начинают достраиваться. Сторона каждого следующего N-угольника содержит ровно на 1 комнату больше, чем сторона предыдущего. При этом каждый из N-угольников содержит такое же количество угловых вершин, что и предыдущий. Оно равно N-1.
Отсюда выводим формулу
#include
unsigned long long n, k, r = 0, s, i;
int main(){
std::cin >> n >> k;
for(i = 0; i < k; i++)
r += i;
s = 1 + (n-1)*k + (n-2)*r;
std::cout <}
Слайд 87. Замок – решение #2
#include
unsigned long long n, k;
int main(){
std::cin >> n >> k;
std::cout << 1 + k*(n-1)+k*(k-1)*(n-2)/2;
}
Слайд 98. Кубы 888 - условие
Все натуральные числа, кубы которых, заканчиваются на
888, образуют возрастающую последовательность
a1 = 192
…
a4 = 942
…
По номеру элемента последовательности найти его значение
Входные данные
Одно число N из диапазона 1< N ≤ 1000000.
Выходные данные
Одно число – значение aN элемента последовательности с номером N.
Слайд 109. Кубы 888 – решение #1
#include
using namespace std;
int main(){
int n, i = 1;
cin >> n;
while (n){
i++;
if(i * i * i % 1000 == 888) n--;
}
cout << i;
return 0;
}
Слайд 1110. Кубы 888 – решение #2
Однако эта программа является вспомогательной и
полезна при определении закономерности:
a1=192; a2=442; a3=692; a4=942; a5=1192…
#include
int main() {
int n;
std::cin >> n;
std::cout << 192 + 250*(n-1);
return 0;
}
Слайд 1211. Матфункции вместо циклов
Бинарные числа (acmp.ru)
Говорят, что плохой программист – это
тот, кто считает, что в одном килобайте 1000 байт, а хороший программист – это тот, кто полагает, что в одном километре 1024 метра.
Многим эта шутка понятна, так как все знают, что в процессах, связанных с информатикой и компьютерной техникой, фигурирует множество значений, выражаемых степенью двойки, то есть чисел вида 2K, где K – некоторое неотрицательное целое число. Назовем такие числа бинарными. Это такие числа как 2, 4, 8, 16, 32 и т.д.
Входные данные
N, не превосходящее 10000 по абсолютной величине.
Выходные данные
Выведите YES, если заданное число является бинарным, и NO в противном случае.
Примеры
№ Ввод Вывод
1 1024 YES
2 23 NO
Слайд 1312. Бинарные числа - решение
#include
#include //функция log2
using namespace std;
int
main() {
int a;
double b;
cin >> a;
a = b = log2(a);
cout << (b == a ? "YES" : "NO");
}
Слайд 1413. Подсчет количества цифр
Определить количество цифр в натуральном числе x, не
превышающем 109.
#include
#include
int main(){
unsigned int x, cnt_digit;
std::cin >> x;
cnt_digit = log10(x) + 1;
std::cout << cnt_digit;
}
Слайд 1514. Суммы рядов
Садовник (e-olymp)
Садовник посадил за день N деревьев и должен
был вылить под каждое деревцо по ведру воды. Так как в день посадки шёл дождь, садовник начал поливку деревьев не в день посадки, а начиная с какого-то K-го дня.
Сколько дней садовник не поливал деревья, если в последний день он под каждое из деревьев вылил 1 / N часть воды из ведра, в предпоследний - 1 / (N - 1) часть, и т.д., а всего под каждое из деревьев вылил не более чем по половине ведра воды?
Входные данные
Количество деревьев N. 0 < N ≤ 1000000
Выходные данные
Искомое количество дней.
Пример
Ввод Вывод
3 2
1000000 606531
Слайд 1615. Садовник – решение #1 с циклами
#include
int main() {
int n, k, days = 0;
double waterPerTree = 0;
std::cin >> n;
while(waterPerTree <= 0.5)
waterPerTree += (double) 1 /
(n - days++);
k = n - days +1;
std::cout << k;
}
Слайд 1716. Садовник – решение #2
Воспользуемся для этого свойствами гармонических рядов. Для
нахождения их частичных сумм можно применить приближенную формулу Эйлера.
где n – количество членов ряда, а γ0,577 215 – постоянная Эйлера
#include
#include
int main() {
double n;
std::cin >> n;
std::cout << round(exp(log(n) +
0.41 / n - 0.5));
}
Слайд 1817. Замечательные числа
Назовем целое число N замечательным, если для него справедливо
равенство:
N² = (N – 1)² + M²,
где М – целое число. Даны два целых числа А и В. Найти количество замечательных чисел из диапазона [A,B] включительно. Например, в диапазоне [1,10] таких чисел два, а именно числа 1 и 5.
Входные данные
Два целых числа А и В (1 ≤ A ≤ B ≤ 109), разделенных пробелом.
Выходные данные
Единственная строка - количество замечательных чисел из заданного диапазона.
Пример
Ввод Вывод
1 10 2
Слайд 1918. Замечательные числа – решение #1
Первое интуитивное решение задачи состоит в
переборе возможных пар N и M для каждого числа диапазона [A;B]. Для больших значений B программа получит превышение лимита времени и не сможет пройти тесты. Попробуем оптимизировать перебор.
После несложных преобразований исходного равенства получим
2×N – 1= M²
В левой части этого равенства находится нечетное число, следовательно, М тоже нечетное и принадлежит отрезку от 1 до квадратного корня из числа 2×N–1. Так как максимальное значение N равно В, то правая граница отрезка, содержащего М, равна квадратному корню из числа 2*1000000000-1. Эта величина меньше 50000.
Слайд 2029. Замечательные числа – решение #1
#include
using namespace std;
int main() {
int a, b, cnt = 0, n;
cin >> a >> b;
for(int m = 1; m*m <= 2e9; m += 2){
n = (m*m + 1)/2;
if (a<=n && n<=b) cnt++;
}
cout << cnt;
}
Слайд 2120. Замечательные числа – решение #2
Второй лучший способ состоит в том,
чтобы вообще избавиться от перебора. Найдем наименьшее нечетное m1, такое что
и наибольшее нечетное m2, такое что
Очевидно, нечетных чисел от m1 до m2 всего
Слайд 2221. Замечательные числа – решение #2
#include
using namespace std;
int main() {
int a, b, cnt = 0, n;
cin >> a >> b;
for(int m = 1; m*m <= 2e9; m += 2){
n = (m*m + 1)/2;
if (a<=n && n<=b) cnt++;
}
cout << cnt;
}
Слайд 2322. Оптимизация с помощью битовых операций
Нечётно повторяемый
Задан набор чисел, о которых
известно, что все элементы в нем встречаются четное число раз. За исключением одного, который либо не повторяется, либо встречается нечетное число раз. Найдите этот элемент.
Входные данные
В первой строке одно целое неотрицательное число N≤105– общее количество элементов в наборе.
Во второй строке N целых чисел, по модулю не превосходящих 109.
Выходные данные
Единственное число x, повторяющееся нечетное количество раз.
Пример
Ввод Вывод
7
82 11 82 82 11 14 82 14
Слайд 2423. Нечетно повторяемый – решение
#include
int main(){
int n,
a, s = 0;
std::cin >> n;
while(n--) {
std::cin >> a;
s ^= a;
}
std::cout << s;
}
Слайд 2524. Оптимизация с помощью структур данных
Выборы жрецов
Нечётно повторяемый
Страна состоит из маленьких
графств. Графства объединяются в конфедерации. Каждая конфедерация раз в год выбирает себе покровителя – одного из 200 жрецов. Конфедерации одновременно подают заявления (одно от конфедерации) в Совет Жрецов о том, кого они хотели бы видеть своим покровителем (если заявление не подано, то считают, что конфедерация хочет оставить себе того же покровителя). Все заявки удовлетворяются. Если несколько конфедераций выбирают одного и того же Жреца, то они навсегда объединяются в одну. Таким образом, каждый Жрец всегда является покровителем не более чем одной конфедерации. Требуется написать программу, позволяющую Совету Жрецов выяснить номер Жреца-покровителя каждого графства после Великих Перевыборов. В Совете все графства занумерованы (начиная с 1). Все Жрецы занумерованы числами от 1 до 200 (некоторые из них сейчас могут не быть ничьими покровителями).
Слайд 2625. Выборы жрецов
Входные данные
В первой строке записано число N – количество
графств в стране (1 ≤ N ≤ 5000) – и далее для каждого графства записан номер Жреца-покровителя конфедерации, в которую оно входит (графства считаются по порядку их номеров). Затем указаны заявления от конфедераций. Сначала записано число M – количество поданных заявлений, а затем M пар чисел (1 ≤ M ≤ 200): первое число – номер текущего Жреца-покровителя, второе – номер желаемого Жреца-покровителя.
Все числа во входных данных разделяются пробелами и (или) символами перевода строки.
Выходные данные
Вывести для каждого графства одно число – номер его Жреца-покровителя после Великих Перевыборов. Сначала – для первого графства, затем – для второго и т.д.
Пример
Ввод Вывод
7
1 1 5 3 1 5 1
2
5 1
1 3 3 3 1 3 3 1 3
Слайд 2726. Выборы жрецов – «лобовое» решение
#include
using namespace std;
int n, a[5000],
m, b1[200], b2[200], i, j;
int main(){
cin >> n;
for(i = 0; i < n; i++)
cin >> a[i];
cin >> m;
for (i = 0; i < m; i++)
cin >> b1[i] >> b2[i];
for (i = 0; i < n; i++){
for(j = 0; j < m; j++)
if (b1[j]==a[i]) {a[i] = b2[j]; break;}
cout << a[i] << ' ';
}
}
Слайд 2827. Выборы жрецов – ассоциативный массив
#include
using namespace std;
int n, m,
a[5000], b[201] = {0,}, i;
int main() {
cin >> n;
for(i = 0; i < n; i++)
cin >> a[i];
cin >> m;
while(m--) {
cin >> i;
cin >> b[i];
}
for (i = 0; i < n; i++)
cout << (b[a[i]] ? b[a[i]] : a[i]) << ' ';
}
Слайд 2929. Лексикографический порядок
Составить число из двух
Имеется два натуральных числа A и
B. Записать их рядом, так чтобы получилось наибольшее число
Входные данные
Два числа, не превышающих 109, записаны через пробел.
Выходные данные
Одно число, составленное из первых двух, записанных рядом
Пример
Ввод Вывод
159 23 23159
Слайд 3029. Составить из двух - решение
#include
using namespace std;
int main(){
string a,
b;
cin >> a >> b;
cout << max(a,b) << min(a,b);
}
Слайд 3130. Проверка периодичности
Снова Фибоначчи (acmp.ru)
Вам наверняка знакомы числа Фибоначчи:
1, 1,
2, 3, 5, 8, 13, 21...
Они определяются рекуррентным соотношением:
Fn = Fn-1 + Fn-2, F0 = F1 = 1.
Требуется найти последнюю цифру n-го числа Фибоначчи.
Входные данные
Одно целое число n (0 ≤ n ≤ 1018).
Выходные данные
Одно число - последнюю цифру числа Fn.
Примеры
№ Ввод Вывод
1 1 1
2 5 8
Слайд 3231. Снова Фибоначчи- решение
#include
int main() {
unsigned long long
n;
int n, a , b, c;
a = b = c = 1;
scanf("%d", &n);
(n %= 60)--;
if (n < 0) n = 59;
while(n--){
c = a + b;
if (c > 9) c -= 10;
a = b;
b = c;
}
printf("%d", c);
}