|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Главная ● Карта сайта | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Пример 4.2. Степень числа за линейное время. Вычисление степени числа f(a, n) = an с временной оценкой O(n) можно определить при помощи следующего рекуррентного соотношения:
Пример 4.3. Степень числа за логарифмическое время. Вычисление степени числа f(a, n) = an с временной оценкой O(log2n) определим следующим образом:
Пример 4.4. Сумма цифр числа. Сумму цифр натурального числа n можно найти при помощи функции f(n), определенной следующим образом:
Пример 4.5. Число единиц. Количество единиц в двоичном представлении числа n можно вычислить при помощи функции f(n), определенной следующим образом (& - операция побитового ‘И’):
В результате операции n = n & (n – 1) уничтожается последняя единица в двоичном представлении числа n: n = a1a2…ak-1ak10…0 n – 1 = a1a2…ak-1ak01…1 n & (n – 1) = a1a2…ak-1 000…0 Рекурсивный вызов функции f будет совершаться столько раз, сколько единиц в двоичном представлении числа n.
Пример 4.6. Биномиальный коэффициент. Значение биномиального коэффициента равно
и определяется рекуррентным соотношением:
int c(int k, int n) { if (n == k) return 1; if (k == 0) return 1; return c(k-1,n-1) + c(k,n-1); }
Учитывая, что
int c(int k, int n) { int i,res = 1; for(i=1;i<=k;i++) res = res * (n - i + 1) / i; return res; }
Упражнение 4.1. Рекурсивная функция. Для заданного натурального n вычислить значение функции f(n), заданной рекуррентными соотношениями: f(2 * n) = f(n), f(2 * n + 1) = f(n) + f(n + 1), f(0) = 0, f(1) = 1
Упражнение 4.2. Функция Аккермана. Функция Аккермана A(m, n) определяется рекурсивно следующим образом: A(0, n) = n + 1, A(m, 0) = A(m – 1, 1), A(m, n) = A(m – 1, A(m, n – 1)) Реализовать функцию A(m, n) и протестировать ее для разных входных значений аргументов.
Упражнение 4.3. Отбор в разведку [ACM, 1999]. Из n солдат, выстроенных в шеренгу, требуется отобрать нескольких в разведку. Для того, чтобы сделать это, выполняется следующая операция: если солдат в шеренге больше чем 3, то удаляются все солдаты стоящие на четных позициях, или все солдаты, стоящие на нечетных позициях. Эта процедура повторяется до тех пор, пока в шеренге останется 3 или менее солдат. Их и отсылают в разведку. Вычислить количество способов, которыми могут быть сформированы таким образом группы разведчиков ровно из трех человек. Вход. Количество солдат в шеренге n ( 0 < n £ 107). Выход. Количество способов, которыми можно отобрать солдат в разведку описанным выше способом.
Упражнение 4.4 [Вальядолид, 374]. Большой модуль. По заданным b, p, m вычислить значение выражения bp mod m. Вход. Содержит несколько тестов. Числа b, p, m находятся в отдельных строках. Известно, что 0 £ b, p £ 2147483647, 1 £ m £ 46340. Выход. Для каждого теста вывести в отдельной строке значение bp mod m.
Упражнение 4.5 [Вальядолид, 10696]. f91. Вычислить значение функции f91, заданной рекуррентным соотношением:
f91(n) =
Вход. Каждая входная строка содержит натуральное число n (n £ 1000000). Число n = 0 является концом входных данных и не обрабатывается. Выход. Для каждого входного n вывести значение f91(n) как показано в примере ниже.
Упражнение 4.6 [Вальядолид, 10774]. Повторяющийся Иосиф. По кругу стоят n людей, занумерованных от 1 до n. Начиная отсчет с первого и двигаясь по кругу, будем казнить каждого второго человека до тех пор пока не останется один. Пусть этот выживший имеет номер x. Расставим по кругу x людей и повторим процедуру, после которой выживет человек с номером y. И так далее до тех пор, пока номер выжившего не станет равным первоначальному количеству людей в текущем раунде. Например, при n = 5 последовательно будут казнены 2, 4, 1, 5. Выживет номер 3. Он не равен 5 (количеству людей в раунде), поэтому следует повторить процедуру. Для n = 3 казнены будут 2, 1. Выживет человек с номером 3, равным n. Процедура заканчивается. Вход. Входные данные состоят из нескольких тестов. Каждый тест в одной строке содержит одно число n (0 < n £ 30000) Выход. Для каждого теста вывести в отдельной строке его номер как указано в примере, количество повторений процедуры казни после первой итерации и номер выжившего в конце процедуры.
Упражнение 4.7 [Вальядолид, 10994]. Простое сложение. Определим рекурсивную функцию f(n) следующим образом:
f(n) = Определим функцию S(p, q) следующим образом:
S(p, q) =
В задаче необходимо вычислить значение S(p, q) по заданным p и q. Вход. Каждая строка содержит два неотрицательных 32-битовых знаковых числа p и q (p £ q). Последняя строка содержит два отрицательных целых числа и не обрабатывается. Выход. Для каждой пары p и q вывести значение S(p, q).
УКАЗАНИЯ К РЕШЕНИЮ УПРАЖНЕНИЙ
Упражнение 3.1. Функция вычисления модуля действительного числа имеет вид:
#include <stdio.h> double x,y; double _fabs(double x) { return (x >= 0) ? x : -x; } void main() { x = -5.65; printf("%lf\n",_fabs(x)); }
Упражнение 4.1. Рекурсивная функция. Непосредственная реализация функции f(n) имеет вид:
int f(int n) { if (n <= 1) return n; if (n % 2) return f(n/2) + f(n/2+1); return f(n/2); }
При такой реализации некоторые значения функции f могут быть вызваны несколько раз. Определим функцию g(n, i, j) = i * f(n) + j * f(n + 1), для которой имеют место равенства: g(2 * n, i, j) = g(n, i + j, j), g(2 * n + 1, i, j) = g(n, i, i + j), g(0, i, j) = i * f(0) + j * f(1) = j Используя приведенные соотношения, можно вычислить значение f(n) = g(n, 1, 0) с временной оценкой O(log n).
int g(int n, int i, int j) { if (!n) return j; if (n % 2) return g(n/2, i, i + j); return g(n/2, i + j, j); }
int f(int n) { return g(n,1,0); }
Упражнение 4.2. Функция Аккермана. Рекурсивная реализация функции Аккермана имеет вид:
int a(int m, int n) { if (!m) return n+1; if (!n) return a(m-1,1); return a(m-1,a(m,n-1)); }
Для малых значений m функцию Аккермана можно выразить явно: A(0, n) = n + 1 A(1, n) = 2 + (n + 3) – 3 = n + 2 A(2, n) = 2 * (n + 3) – 3 = 2 * n + 3 A(3, n) = 2n + 3 – 3
Упражнение 4.3. Отбор в разведку [ACM, 1999]. Обозначим через f(n) количество способов, которыми можно сформировать группы разведчиков из n человек в шеренге. Поскольку нас интересуют только группы по три разведчика, то f(1) = 0, f(2) = 0, f(3) = 1. То есть из трех человек можно сформировать только одну группу, из одного или двух – ни одной. Если n четное, то применяя определенную в задаче операцию удаления солдат в шеренге, мы получим в качестве оставшихся либо n / 2 солдат стоящих на четных позициях, либо n / 2 солдат, стоящих на нечетных позициях. То есть f(n) = 2 * f(n / 2) при четном n. Если n нечетное, то после удаления останется либо n / 2 солдат стоявших на четных позициях, либо n / 2 + 1 солдат, стоявших на нечетных позициях. Общее количество способов при нечетном n равно f(n) = f(n / 2) + f(n / 2 + 1). Таким образом, получена рекуррентная формула для вычисления значения f(n): f(n) = 2 * f(n / 2), если n четное f(n) = f(n / 2) + f(n / 2 + 1), если n нечетное f(1) = 0, f(2) = 0, f(3) = 1 Реализация функции f имеет вид:
int f(int n) { if (n <= 2) return 0; if (n == 3) return 1; if (n % 2) return f(n/2) + f(n/2+1); return 2*f(n/2); }
Упражнение 4.4 [Вальядолид, 374]. Большой модуль. Из ограничений на входные данные следует, что в процессе вычисления достаточно использовать тип данных int. Возведение в степень bp будем производить с логарифмической временной сложностью O(log p) используя алгоритм, базирующийся на двоичном разложении показателя степени p:
bp =
Пример. Для вычисления значения из первого теста 318132 (mod 17) следует представить показатель степени в двоичной системе исчисления: 1813210 = 1000110110101002. Далее 318132 (mod 17) = 316384 * 31024 * 3512 * 3128 * 364 * 316 * 34 (mod 17) = 13.Для второго теста 171765 (mod 3) = (17 mod 3) 1765 (mod 3) = 2 1765 (mod 3) = 2.
Реализация. Функция pow вычисляет выражение bp mod m с оценкой сложности O(log p).
#include <stdio.h>
int b,p,m,res;
int pow(int b, int p, int m) { int res=1; while(p > 0) { if (p & 1) res = (res * b) % m; p >>= 1; b = (b * b) % m; } return res; }
void main(void) {
Прочитав входные значения b, p и m, следует воспользоваться формулой bp mod m = (b mod m)p mod m При передаче параметров функции pow основание степени b должно быть не больше чем модуль m. Если этого не сделать, получим Time Limit. Отдельно следует обработать случай, когда p = 0: b0 mod m = 1.
while (scanf("%d %d %d",&b,&p, &m) == 3) { b = b % m; if (!p) res = 1; else res = pow(b,p,m); printf("%d\n",res); } }
Упражнение 4.5 [Вальядолид, 10696]. f91. Необходимо вычислить значения функции f91(n) для n £ 100. Имеем: f91(100) = f91(f91(111)) = f91(101) = 91, f91(99) = f91(f91(110)) = f91(100) = 91. Аналогично продолжая, можно заметить что f91(n) = 91, где 1 £ n £ 100. Таким образом, имеет место соотношение:
f91(n) =
Реализация. Читаем входные значения n, пока не встретится 0. Выводим результат согласно приведенному выше соотношению.
#include <stdio.h>
int n,res;
void main(void) { while(scanf("%d",&n), n != 0) { if (n >= 101) res = n-10; else res = 91; printf("f91(%d) = %d\n",n,res); } }
Упражнение 4.6 [Вальядолид, 10774]. Повторяющийся Иосиф. Пусть n – количество людей в круге. Обозначим через f(n) номер последнего уцелевшего. Положим f(1) = 1. Если n = 2 * k – четное, то после прохода первого круга будут удалены люди с четными номерами: 2, 4, ..., 2 * k. Останутся люди с нечетными номерами, а отсчет продолжаем с номера 1. Это все равно, что если бы у нас было k людей, а номер каждого удвоился и уменьшился на 1. То есть получим соотношение f(2 * k) = 2 * f(k) – 1.
Если n = 2 * k + 1 – нечетное, то после прохода первого круга будут удалены люди с четными номерами 2, 4, ..., 2 * k, а жертва с номером 1 уничтожается сразу же после жертвы с номером 2 * k. Остается k людей с номерами 3, 5, 7, …, 2 * k + 1. Это все равно, что люди занумерованы от 1 до k, только номер каждого удвоился и увеличился на 1. Получаем соотношение: f(2 * k + 1) = 2 * f(k) + 1. Объединяя полученные соотношения, получим рекуррентность: f(1) = 1 f(2 * k) = 2 * f(k) – 1, k ³ 1 f(2 * k + 1) = 2 * f(k) + 1, k ³ 1 Теорема. Значение f(n) получается путем циклического сдвига двоичного представления n влево на один бит. Например, f(100) = f(11001002) = 10010012 = 73. Многократное применение функции f порождает последовательность убывающих значений, достигающих неподвижной точки n такой что f(n) = n. Число n будет состоять из одних единиц со значением 2v(n) – 1, где v(n) – количество единиц в бинарном представлении числа n. Пример. Рассмотрим входные данные для второго теста. При n = 13 последовательно будут казнены 2, 4, 6, 8, 10, 12, 1, 5, 9, 13, 7, 3. Выживет номер 11. Он не равен 13 (количеству людей в раунде), поэтому следует повторить процедуру. Для n = 11 казнены будут 2, 4, 6, 8, 10, 1, 5, 9, 3, 11. Выживет человек с номером 7, равным n. При n = 7 выживет номер 7. После первой итерации проведено еще 2 повторения процедуры казни. Реализация. Функция last по первоначальному количеству людей n в круге возвращает номер уцелевшего согласно рекуррентному соотношению.
#include <stdio.h>
int k,n,i,r,tests;
int last(int n) { if (n == 1) return 1; if (n%2 == 0) return 2*last(n/2)-1; else return 2*last((n-1)/2)+1; }
void main(void) { scanf("%d",&tests); for(i=1;i<=tests;i++) {
Переменная r содержит количество повторений процедуры казни (изначально r = 0). По заданному входному n ищем номер уцелевшего k. Если он не равен n, то повторяем в цикле процедуру казни.
scanf("%d",&n); r = 0; while ((k = last(n)) != n) r++, n = k; printf("Case %d: %d %d\n",i,r,n); } }
Упражнение 4.7 [Вальядолид, 10994]. Простое сложение. Приведенная в условии функция f(n) находит последнюю ненулевую цифру числа n. Обозначим через
g(p) = Тогда S(p, q) = g(q) – q(p – 1). Для вычисления функции g(p), суммы последних значащих цифр для чисел от 1 до p, разобьем числа от 1 до p на три множества (операция деления ‘/’ является целочисленной): 1. Числа от (p/10)*10 + 1 до p; 2. Числа от 1 до (p/10)*10, не оканчивающиеся нулем; 3. Числа от 1 до (p/10)*10, оканчивающиеся нулем; Например, при p = 32 к первому множеству отнесутся числа 31, 32, ко второму 1, …, 9, 11, …, 19, 21, …, 29, к третьему 10, 20. Сумма последних значащих цифр в первом множестве равна 1 + 2 + … + p%10 = t(1 + t) / 2, где t = p % 10. Во втором множестве искомая сумма равна p / 10 * 45, так как сумма всех цифр от 1 до 9 равна 45, а число полных десятков равно p / 10. Требуемую сумму для третьего множества найдем рекурсивно: она равна g(p / 10). Реализация. Поскольку выполняется обработка 32-битовых знаковых чисел, то при вычислениях используем тип long long (__int64).
#include <stdio.h>
long long p,q;
Функция g(p) вычисляет сумму значений функции f(n) для значений аргумента n от 1 до p.
long long g(long long p) { long long t = p % 10; if (!p) return 0; return t*(1+t)/2 + p/10 * 45 + g(p/10); return 0; }
Значение функции S(p, q) считаем как g(q) – q(p – 1).
long long s(long long p, long long q) { return g(q) - g(p-1); }
void main(void) {
Основной цикл программы. Для каждой пары чисел p и q выводим значение s(p, q).
while(scanf("%lld %lld",&p,&q),p+q>=0) printf("%lld\n",s(p,q)); }
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
При копировании материалов обратная ссылка обязательна Информатика в школе 2005-2017 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||