You are here
Посчитаем цыплят по осени или немного мат.програмирования
Друзья подкинули задачку из кружка математики для школьников. Инженерный калькулятор для неё не годится. А программируемому может не хватить процессорных силёнок.
Три фермера продавали кур на местном рынке. У одного было 10 кур, у второго — 16, у третьего — 26. Чтобы не конкурировать между собой, они договорились продавать кур по одной цене. К обеду они решили, что продажи идут не так уж хорошо, поэтому они все одинаково понизили цену. К концу дня они продали всех кур. Оказалось, что каждый из фермеров за этот день выручил 35 долларов. Какова была цена за курицу до обеда и после обеда?
Обозначим цены как X и Y, а количества проданных кур, соответственно, до и после обеда i-м фермером как Ai и Bi. Получается система линейных уравнений:
A1*X + (10 - A1)*Y = 3500 A2*X + (16 - A2)*Y = 3500 A3*X + (26 - A3)*Y = 3500
Дополнительные условия:
- точность до центов;
- 0>X>3500;
- 0>Y>3500;
- X>Y;
- куры продаются только целиком.
Это позволяет немного упростить задачу и решить её в целых числах.
Небольшой объем позволяет решить задачу полностью путем перебора. Вот соответствующая программа на FreePascal.
program Chickens; var a1, a2, a3: integer; x, y: integer; begin for x := 1 to 3499 do for y := 1 to x - 1 do for a1 := 0 to 10 do for a2 := 0 to 16 do for a3 := 0 to 26 do if (a1 * x + (10 - a1) * y = 3500) and (a2 * x + (16 - a2) * y = 3500) and (a3 * x + (26 - a3) * y = 3500) then writeln('x = ', x, ', y = ', y, ', a1 = ', a1, ', b1 = ', 10 - a1, ', a2 = ', a2, ', b2 = ', 16 - a2, ', a3 = ', a3, ', b3 = ', 26 - a3 ); writeln('Finished'); readln; end.
Программа считает достаточно долго, несколько минут на двуядерном Intel 2,2 ГГц (что хорошо иллюстрирует полную неразрешимость задач из обычной логистики даже средствами современных суперкомпьютеров). Ответ:
x = 375, y = 125, a1 = 9, b1 = 1, a2 = 6, b2 = 10, a3 = 1, b3 = 25
Вопросы любителям вычислений:
- Сколько считает программа на вашем калькуляторе?
- Какие методы линейного программирования позволят сократить счет, чтобы получить хотя бы одно решение за минимальное время?
Дополнение: тесты производительности
Тот же текст программы можно без изменений запустить в Delphi (у меня древняя версия 7). Для Си программу надо слегка переделать.
#include <stdio.h> #include <stdlib.h> int main() { int a1, a2, a3; int x, y; for (x = 1; x < 3500; x++) for (y = 1; y < x; y++) for (a1 = 0; a1 <= 10; a1++) for (a2 = 0; a2 <= 16; a2++) for (a3 = 0; a3 <= 26; a3++) if ((a1 * x + (10 - a1) * y == 3500) && (a2 * x + (16 - a2) * y == 3500) && (a3 * x + (26 - a3) * y == 3500)) printf("x = %d, y = %d, a1 = %d, b1 = %d, a2 = %d, b2 = %d, a3 = %d, b3 = %d\n", x, y, a1, 10 - a1, a2, 16 - a2, a3, 26 - a3); printf("Finished\n"); return 0; }
Результаты на однопроцессорной виртуальной машине под Windows XP:
- FreePascal 2.6 : 75 сек
- Delphi 7 : 55 сек
- GNU C 4.6 : 38 сек
Для C# программу также надо немного переделать
using System; namespace chicken_csharp { class MainClass { public static void Main (string[] args) { DateTime d1 = DateTime.Now; int a1, a2, a3; int x, y; for (x = 1; x < 3500; x++) for (y = 1; y < x; y++) for (a1 = 0; a1 <= 10; a1++) for (a2 = 0; a2 <= 16; a2++) for (a3 = 0; a3 <= 26; a3++) if ((a1 * x + (10 - a1) * y == 3500) && (a2 * x + (16 - a2) * y == 3500) && (a3 * x + (26 - a3) * y == 3500)) Console.WriteLine ("x = {0}, y = {1}, a1 = {2}, b1 = {3}, a2 = {4}, b2 = {5}, a3 = {6}, b3 = {7}", x, y, a1, 10 - a1, a2, 16 - a2, a3, 26 - a3); Console.WriteLine ("Finished"); Console.WriteLine ("Elapsed sec {0}", DateTime.Now.Subtract (d1).TotalSeconds); } } }
Результаты на 4-ядерном ПК под Ubuntu 12.04 x64 (используется только 1 ядро судя по монитору):
- FreePascal 2.6 : 58 сек
- C# Mono/.NET 4 : 53 сек
- GNU C 4.6 : 29 сек
Во всех случаях включена оптимизация, отключена отладочная информация.
Си даже в его GNU-той реализации показывают впечатляющее качество генерируемого кода.
Есть желающие сравнить с другими интерпретаторами и байткод-компиляторами типа Явы?
Оптимизация 1
В первоначальной программе можно сделать простую оптимизацию: в следующий уровень цикла входим только если условие "все куры i-го продавца проданы за 3500 центов".
program Chickens; var a1, a2, a3: integer; x, y: integer; begin for x := 1 to 3499 do for y := 1 to x - 1 do for a1 := 0 to 10 do if (a1 * x + (10 - a1) * y = 3500) then for a2 := 0 to 16 do if (a2 * x + (16 - a2) * y = 3500) then for a3 := 0 to 26 do if (a3 * x + (26 - a3) * y = 3500) then writeln('x = ', x, ', y = ', y, ', a1 = ', a1, ', b1 = ', 10 - a1, ', a2 = ', a2, ', b2 = ', 16 - a2, ', a3 = ', a3, ', b3 = ', 26 - a3); writeln('Finished'); readln; end.
Время счета снижается с 58 до примерно 0,5 секунды, то есть на 2 порядка.
Оптимизация 2
Цену Y можно вычислять, как функцию от переменных (a1, x). Для наглядности я закомментировал "лишний" цикл, а значение цену Y=-1 используется как любое (весь товар был продано i-м торговцем ещё до обеда). Необходимо в этих случаях добавить проверки деления на ноль.
program Chickens; var a1, a2, a3: integer; x, y: integer; begin for x := 1 to 3499 do // for y := 1 to x - 1 do for a1 := 0 to 10 do if ((a1 * x) <= 3500) and ((a1 = 10) or ((3500 - (a1 * x)) mod (10 - a1) = 0)) then begin if a1 = 10 then y := -1 // цена может быть любой else y := (3500 - (a1 * x)) div (10 - a1); if (y = 0) or (x <= y) then continue; for a2 := 0 to 16 do begin if y = -1 then if a2 = 16 then y := -1 else if (((a2 * x) <= 3500) and ((3500 - (a2 * x)) mod (16 - a2) = 0)) then y := (3500 - (a2 * x)) div (16 - a2) else continue; if (y = 0) or (x <= y) then break; if ((a2 * x) <= 3500) and ((y = -1) or (a2 * x + (16 - a2) * y = 3500)) then for a3 := 0 to 26 do begin if y = -1 then if a3 = 26 then y := -1 else if (((a3 * x) <= 3500) and ((3500 - (a3 * x)) mod (26 - a3) = 0)) then y := (3500 - (a3 * x)) div (26 - a3) else continue; if (y = 0) or (x <= y) then break; if (a3 * x + (26 - a3) * y = 3500) then writeln('x = ', x, ', y = ', y, ', a1 = ', a1, ', b1 = ', 10 - a1, ', check S1 = ', a1 * x + (10 - a1) * y, ', a2 = ', a2, ', b2 = ', 16 - a2, ', check S2 = ', a2 * x + (16 - a2) * y, ', a3 = ', a3, ', b3 = ', 26 - a3, ', check S3 = ', a3 * x + (26 - a3) * y ); end; end; end; writeln('Finished'); end.
Время счета снижается с 58 до примерно 0,001 секунды, то есть на 4-5 порядков.
Comments
Приведённая
Приведённая Вами задача не является задачей линейного программирования, во-первых, потому, что отсутствует целевая функция, а во-вторых, потому, что система ограничений не является линейной системой уравнений (присутствуют произведения переменных).
Вашу программу можно немного оптимизировать, если учесть имеющуюся в условии информацию о ценах до и после обеда, а именно, что x>y.
Методов решения классической задачи линейного программирования несколько, но все они требуют наличия целевой функции.
P.S. Запустил программку на HP Prime, считает уже полчаса. Результаты выложу позже.
Была опечатка
Была опечатка в третьем операторе сравнения, я исправил, добавил оптимизацию X>Y. Решение получается одно. ЦФ здесь - полная распродажа товара :)
А вообще налицо
А вообще налицо сговор. Антимонопольный комитет дремлет :)
<удалено>
.
Для HP Prime
Для HP Prime пришлось ещё раз оптимизировать программку, т.к. в указанном варианте время счёта ОЧЕНЬ велико (задачка не для калькулятора). Теперь программка отбрасывает часть вариантов, среди которых гарантированно нет решения. Предполагаемое время счёта от 15 до 30 часов.
Текст программы:
Для
Для оптимизации можно использовать условие B
HP 50g по
HP 50g по оптимизированной программе посчитал примерно за 15-16 минут.
Текст программы KURKI:
Добавил
Добавил в текст оптимизированный вариант, на HP 50g должен считать за секунды.
Проверил Ваш
Проверил Ваш вариант оптимизированной программки. HP 50g посчитал за 18 секунд: решение выдаёт через секунду, а остаток вариантов проверяет 17 секунд.
Добавил
Добавил еще одну оптимизацию :)
У HP Prime время
У HP Prime время расчёта по программе, которая на HP 50g выполняется 18 секунд, составило примерно 75 минут.
Наверняка
Наверняка какая-то ошибка в программе или программы разные.
Ошибку не
Ошибку не нашёл, а программы такие:
Запустил программку на HP Prime ещё раз, результат прежний (75 минут плюс/минус пара секунд). Решение появляется через 52 секунды. На эмуляторе результат появляется примерно через 5 секунд, а общее время счёта около 6 с половиной минут.
А, так это
А, так это программа на Си для HP 50g, я-то думал, что на UserRPL. Тогда результаты объяснимые. Со второй оптимизацией время должно снизиться еще раз в 100.
<удалено>
.
Сегодня после
Сегодня после работы попробую написать и запустить на HP 50g (опять на C) и на HP Prime программку со второй оптимизацией.
P.S. Если бы это была задача линейного программирования, то можно было попробовать некоторые алгоритмы. Упоминание линейного программирования в названии этой записи некорректно (прошу прощения за нахальство). Правильнее говорить либо о целочисленном программировании, либо о математическом или нелинейном программировании.
P.P.S. По следующей ссылке, вероятно, один из возможных первоисточников задачи (есть интересные комментарии).
Не настаиваю
Не настаиваю, пусть будет матпрограммирование. Я сходу затруднился однозначно классифицировать задачу.
python
Заинтересовался. Набросал программку на питоне. Алгоритм таков: для первого продавца, у которого 10 кур, методом перебора находится совпадение суммы выручки до обеда и выручки после обеда с числом 3500. Затем эти цены подставляются двум другим продавцам. Если и у них сумма 3500, то выдается распечатка, кто, сколько и почем продавал. Программа не оптимизирована, находит первый верный вариант, а затем ещё 4 варианта, когда до обеда продавцы продавали своих кур, а после обеда плюнули на всё и раздали оставшихся бесплатно, т.е. цена у = 0 :)
#! /usr/bin/env python
# -*- coding: utf8 -*-
def other(x, y, n):
for a in range(n):
if a * x + (n - a) * y == 3500: return (True, a, n - a)
return (False, 0, 0)
for x in range(3499):
for y in range (x):
for a1 in range(10):
if a1 * x + (10 - a1) * y == 3500 :
b = other(x, y, 16)
c = other(x, y, 26)
if b[0] and c[0]:
print 'a1 = ', a1, 'x = ', x, 'b1 = ', 10 - a1, 'y = ', y
print 'a2 = ', b[1], 'x = ', x, 'b2 = ', b[2], 'y = ', y
print 'a3 = ', c[1], 'x = ', x, 'b3 = ', c[2], 'y = ', y
print '\n\n'
На моём HP550, linuxmint 17 первый вариант найден примерно за 1 секунду. Весь перебор занимает 27.641 сек.
Задача
Задача заинтересовала. Написал решение для HP-42s. Использовал Free42 на Андроиде. На стареньком телефоне Samsung Galaxy S5660 находит решение и перебирает все остальные варианты почти мгновенно. Думаю, на реальном калькуляторе работала бы тоже не очень долго.
00 { 214-Byte Prgm }
01>LBL "CHICK"
02 135
03 STO "Y"
04>LBL "Y" @ начало цикла Y: 135..1 (3500/26 ~ 135 - максимальный Y)
05 10
06 STO "A1"
07>LBL "A1" @ начало цикла A1: 10..1 (если A1 = 0, то Y = 350, что > 135)
08 3500 @ X = (3500 - 10*Y + A1*Y)/A1
09 10
10 RCL "Y"
11 ×
12 -
13 RCL "A1"
14 RCL "Y"
15 ×
16 +
17 RCL ST X
18 RCL "A1"
19 MOD
20 X!=0?
21 GTO 99
22 Rv
23 LASTX
24 ÷
25 X<=0?
26 GTO 99
27 RCL "Y"
28 X>=Y?
29 GTO 99
30 Rv
31 STO "X"
32 3500 @ A2 = (3500-16*Y)/(X-Y)
33 16
34 RCL "Y"
35 ×
36 -
37 RCL ST X
38 RCL "X"
39 RCL "Y"
40 -
41 MOD
42 X!=0?
43 GTO 99
44 Rv
45 LASTX
46 ÷
47 X<=0?
48 GTO 99
49 16
50 X<Y?
51 GTO 99
52 Rv
53 STO "A2"
54 3500 @ A3 = (3500-26*Y)/(X-Y)
55 26
56 RCL "Y"
57 ×
58 -
59 RCL ST X
60 RCL "X"
61 RCL "Y"
62 -
63 MOD
64 X!=0?
65 GTO 99
66 Rv
67 LASTX
68 ÷
69 X<=0?
70 GTO 99
71 26
72 X<Y?
73 GTO 99
74 Rv
75 STO "A3"
76 RCL "A2"
77 RCL "A1"
78 STOP @ остановка при нахождении решения, продолжение вычислений по R/S
79>LBL 99
80 DSE "A1" @ конец цикла A1
81 GTO "A1"
82 DSE "Y" @ конец цикла Y
83 GTO "Y"
84 99999999 @ конец вычислений
85 STOP
86 END
Как можно
Как можно загнать проще всего загнать эту программу во Free42?
Проще всего
Проще всего через импорт. Залил программу сюда:chick.raw
A*X + (10 - A)*Y = 3500 B*X
A*X + (10 - A)*Y = 3500
B*X + (16 - B)*Y = 3500
C*X + (26 - C)*Y = 3500
Если учесть что X>Y и всегда будет соблюдаться условие (A>B>C при A<=10) - Это условие не может не соблюдаться,как в свою очередь X не может быть меньше 350 (X>=350),то можно написать программу на CASIO 9860
For 350→X To 3499
For 1→Y To X-1
X-Y→Z
For 1→A To 10
If A*Z+10*Y=3500
Then
For 1→B To 9
If B*Z+16*Y=3500
Then
For 1→C To 8
If C*Z+26*Y=3500
Then
X¸
Y¸
A¸
B¸
C¸
IfEnd
Next
IfEnd
Next
IfEnd
Next
Next
Next
Время до появления результатов 7:50 , при разогнанном процесоре в режиме (Quadruple)
время 4:10
Есть еще старая задача 3-го класса церковно-приходской школы ,когда надо было на 100 копеек
купить 100 животных. Корова стоила 5 копеек. овца 3 копейки и десяток гусей стоил 1 копейку.
задача 3-го класса церковно-приходской школы
[quote=Ardo_79]Есть еще старая задача 3-го класса церковно-приходской школы ,когда надо было на 100 копеек
купить 100 животных. Корова стоила 5 копеек. овца 3 копейки и десяток гусей стоил 1 копейку.[/quote]Ответ: 16 коров, 4 овцы и 80 гусей :)
Решил в Free42, находит мгновенно:
Программа тут
Многобуквенные
Многобуквенные Labels и переменные радуют после HP35s