% Albert Cao
% Pizza Problem - Ordering pizza for the boys.
% https://www.minizinc.org/doc-2.5.3/en/part_2_tutorial.html
% Given:
int: n;
int: m;
set of int: pizzas = 1..n; % n
set of int: coupons = 1..m; % m
array[pizzas] of int: price; % of each pizza
array[coupons] of int: buy; % we must buy a certain number of pizzas to use each coupon
array[coupons] of int: free; % each coupon we use lets us get some number of pizzas free
int: costBound; % the amount of money we have to spend.
% Find:
var set of pizzas: paid; % set of pizzas we will pay for
var set of coupons: used; % set of coupons we will use
array[coupons, pizzas] of var bool: justifies; % holds if pizza p is one of the pizzas purchased to justify using coupon c
array[coupons, pizzas] of var bool: usedFor; % holds if we are getting pizza p free by using coupon c (p = price, c = buy)
var int: cost = sum(p in paid)(price[p]); % the final amount of money spent.
% Constraint 1 - we pay for exactly the pizzas we don't get for free by using coupons.
% For all pizzas p, p is in paid if and only if there is not a coupon c that was used to get p.
% Defines the set of paid pizzas.
constraint
forall(p in pizzas) (
(p in paid) <-> not exists(c in used)(usedFor[c, p])
);
% Constraint 2 - Used is the set of coupons we use.
% For all coupons c, c is in used if and only if there is a pizza p that was received by using c.
% Defines the set of used coupons.
constraint
forall(c in coupons) (
(c in used) <-> exists(p in pizzas)(usedFor[c, p])
);
% Constraint 3 - Any coupon we used must be justified by enough paid pizzas.
% For all coupons c, if c is in used, then the number of pizzas p that we used to justify applying c is at least as many as the number of pizzas c says we have to buy.
constraint
forall(c in coupons) (
(c in used) -> sum(p in pizzas)(justifies[c, p]) >= (buy[c])
);
% Constraint 4 - No coupon is used for too many pizzas.
% For all coupons c, the number of pizzas we get for free by applying a coupon is at most the number of pizzas c says we're allowed to get free.
constraint
forall(c in coupons) (
sum(p in pizzas)(usedFor[c, p]) <= (free[c])
);
% Constraint 5 - Each free pizza costs at most as much as the cheapest pizza purchased to justify the coupon used.
% For all coupons c, and every 2 pizzas p1, p2 (that are not identical), if we used c to justify getting p1 for free, and we bought p2 to justify applying c,
% then the price of p1 must be at most the price of p2.
constraint
forall(c in coupons, p1, p2 in pizzas where p1 != p2) (
(usedFor[c, p1] /\ justifies[c, p2]) -> (price[p1] <= price[p2])
);
% Constraint 6 - We pay for all pizzas that justify using coupons.
% For all coupons c and pizzas p, if p is used to justify applying c, then we must pay for p.
constraint
forall(c in coupons, p in pizzas) (
justifies[c, p] -> (p in paid)
);
% Constraint 7 - The total cost is not too high.
% The price of the sum over all pizzas p that are paid for is at most costBound.
constraint
sum(p in paid)(price[p]) <= costBound;
% Constraint 8 - Justifies and UsedFor hold only of coupon-pizza pairs.
% For all coupons c and pizzas p, if justifies(c, p) then c has to be a coupon and p has to be a pizza.
% For all coupons c and pizzas p, if usedFor(c, p) then c has to be a coupon and p has to be a pizza.
constraint
forall(c in coupons, p in pizzas) (
(justifies[c, p] -> (1 <= c /\ c <= m /\ 1 <= p /\ p <= n)) /\
(usedFor[c, p] -> (1 <= c /\ c <= m /\ 1 <= p /\ p <= n))
);
% Bonus Constraint - For all coupons c and pizzas p, if c has been used to justify getting p, then it cannot be used again.
% For all coupons c and pizzas p, if justifies(c, p) then there is not a coupon q not equal to c that was used to justify getting p.
constraint
forall(c in coupons, p in pizzas) (
(justifies[c,p] -> not exists(q in coupons where c != q)(justifies[q,p]))
);
% Such that: we get all pizzas, cost is less than or equal to costBound, and coupons are used only if justified.
solve minimize cost;
% Print output.
output ["cost(" ++ show(cost) ++ ")\n" ++ "costBound(" ++ show(costBound) ++ ")\n"];
n = 4;
price = [10,5,20,15];
m = 2;
buy = [1,2];
free = [1,1];
costBound = 50;