N Machines (Codeforces 1753E)
Tags: Greedy, binary search

You have been invited as a production process optimization specialist to some very large company. The company has $$$n$$$ machines at its factory, standing one behind another in the production chain. Each machine can be described in one of the following two ways: $$$(+,~a_i)$$$ or $$$(*,~a_i)$$$.If a workpiece with the value $$$x$$$ is supplied to the machine of kind $$$(+,~a_i)$$$, then the output workpiece has value $$$x + a_i$$$.If a workpiece with the value $$$x$$$ is supplied to the machine of kind $$$(*,~a_i)$$$, then the output workpiece has value $$$x \cdot a_i$$$.The whole production process is as follows. The workpiece with the value $$$1$$$ is supplied to the first machine, then the workpiece obtained after the operation of the first machine is supplied to the second machine, then the workpiece obtained after the operation of the second machine is supplied to the third machine, and so on. The company is not doing very well, so now the value of the resulting product does not exceed $$$2 \cdot 10^9$$$.The directors of the company are not satisfied with the efficiency of the production process and have given you a budget of $$$b$$$ coins to optimize it.To optimize production you can change the order of machines in the chain. Namely, by spending $$$p$$$ coins, you can take any machine of kind $$$(+,~a_i)$$$ and move it to any place in the chain without changing the order of other machines. Also, by spending $$$m$$$ coins, you can take any machine of kind $$$(*,~a_i)$$$ and move it to any place in the chain.What is the maximum value of the resulting product that can be achieved if the total cost of movements that are made should not exceed $$$b$$$ coins?

Input: The first line contains four integers $$$n$$$, $$$b$$$, $$$p$$$ and $$$m$$$ ($$$1 \le n \le 10^6$$$, $$$1 \le b, p, m \le 10^9$$$) — the number of machine at the factory, your budget and costs of movements of both kinds of machines.Each of the following $$$n$$$ lines contains description of a machine. The description begins with one of the following characters: "+" or "*", that denotes the kind of the machine. Then an integer $$$a_i$$$ follows ($$$1 \le a_i \le 2 \cdot 10^9$$$).It's guaranteed that the current value of the resulting product does not exceed $$$2 \cdot 10^9$$$.

Output: Print one integer — the maximum value of the resulting product that can be achieved if the total cost of movements that are made does not exceed $$$b$$$ coins.

Note: In the first example our budget is too low to move machine $$$(*,~2)$$$, but we can move both machines $$$(+,~1)$$$ to the beginning of the chain. So the final chain will be $$$(+,~1)$$$ $$$(+,~1)$$$ $$$(*,~2)$$$. If the workpiece with the value $$$1$$$ is supplied to the first machine, its value will be changed in the following way: $$$1, 2, 3, 6$$$.In the second example we can move only one machine. Let's move machine $$$(+,~2)$$$ to the beginning of the chain. The final chain will be $$$(+,~2)$$$ $$$(*,~2)$$$ $$$(+,~1)$$$ $$$(*,~3)$$$. The value of the workpiece will be changed in the following way: $$$1, 3, 6, 7, 21$$$.In the third example we can place machine $$$(*,~4)$$$ before the machine $$$(*,~5)$$$, and move machine $$$(+,~3)$$$ to the beginning of the chain. The final chain will be $$$(+,~3)$$$ $$$(*,~2)$$$ $$$(+,~1)$$$ $$$(+,~1)$$$ $$$(+,~1)$$$ $$$(+,~1)$$$ $$$(*,~4)$$$ $$$(*,~5)$$$. The value of the workpiece will be changed in the following way: $$$1, 4, 8, 9, 10, 11, 12, 48, 240$$$.

Sample Inputs:
3 2 1 3
* 2
+ 1
+ 1

4 2 2 2
* 2
+ 1
* 3
+ 2

8 2 1 1
* 2
+ 1
* 4
+ 1
+ 1
+ 1
* 5
+ 3

Sample Outputs:
6

21

240

Solution (Python):
from bisect import bisect
import sys
input = sys.stdin.readline
(n, b, p, m) = map(int, input().split())
adds = []
curr = []
mults = []
i = 0
for _ in range(n):
    (t, v) = input().split()
    v = int(v)
    if t == '*':
        if v == 1:
            continue
        curr.sort()
        adds.append(curr)
        mults.append(v)
        curr = []
    else:
        curr.append(v)
curr.sort()
adds.append(curr)
pref = []
for l in adds:
    np = [0]
    for v in l[::-1]:
        np.append(v + np[-1])
    pref.append(np)
y = len(mults)
un_m = sorted(set(mults))
z = len(un_m)
ct_m = [0] * z
for v in mults:
    for i in range(z):
        if un_m[i] == v:
            ct_m[i] += 1
from itertools import product
poss = []
assert len(adds) == y + 1
for tup in product(*[range(ct + 1) for ct in ct_m]):
    rem_adds = (b - m * sum(tup)) // p
    if rem_adds < 0:
        continue
    d = {}
    for i in range(z):
        d[un_m[i]] = tup[i]
    end = 1
    used = [0] * y
    for i in range(y):
        if d[mults[i]]:
            used[i] = 1
            d[mults[i]] -= 1
            end *= mults[i]
    seg_mult = [1]
    for i in range(y - 1, -1, -1):
        if used[i] == 0:
            seg_mult.append(seg_mult[-1] * mults[i])
        else:
            seg_mult.append(seg_mult[-1])
    seg_mult.reverse()
    exc = [seg_mult[0] - v for v in seg_mult]
    init_tot = 0
    for j in range(y + 1):
        if exc[j] != 0:
            init_tot += len(adds[j])
    lo = 0
    hi = 10 ** 18 + 100
    while hi - lo > 1:
        mid = lo + (hi - lo) // 2
        tot = init_tot
        for j in range(y + 1):
            if exc[j] == 0:
                continue
            limit = (mid - 1) // exc[j]
            diff = bisect(adds[j], limit)
            tot -= diff
        if tot >= rem_adds:
            lo = mid
        else:
            hi = mid
    tot = seg_mult[0]
    ct = 0
    for j in range(y + 1):
        tot += pref[j][-1] * seg_mult[j]
        if exc[j] == 0:
            continue
        limit = (lo - 1) // exc[j]
        s_ct = len(adds[j]) - bisect(adds[j], limit)
        tot += pref[j][s_ct] * exc[j]
        ct += s_ct
    if lo != 0:
        assert ct >= rem_adds
        tot -= lo * (ct - rem_adds)
    poss.append(tot * end)
print(max(poss))

Solution (Python):
from bisect import bisect
import sys
input = sys.stdin.readline
(n, b, p, m) = map(int, input().split())
adds = []
curr = []
mults = []
i = 0
for _ in range(n):
    (t, v) = input().split()
    v = int(v)
    if t == '*':
        if v == 1:
            continue
        curr.sort()
        adds.append(curr)
        mults.append(v)
        curr = []
    else:
        curr.append(v)
curr.sort()
adds.append(curr)
pref = []
for l in adds:
    np = [0]
    for v in l[::-1]:
        np.append(v + np[-1])
    pref.append(np)
y = len(mults)
un_m = sorted(set(mults))
z = len(un_m)
ct_m = [0] * z
for v in mults:
    for i in range(z):
        if un_m[i] == v:
            ct_m[i] += 1
from itertools import product
poss = []
assert len(adds) == y + 1
for tup in product(*[range(ct + 1) for ct in ct_m]):
    rem_adds = (b - m * sum(tup)) // p
    if rem_adds < 0:
        continue
    d = {}
    for i in range(z):
        d[un_m[i]] = tup[i]
    end = 1
    used = [0] * y
    for i in range(y):
        if d[mults[i]]:
            used[i] = 1
            d[mults[i]] -= 1
            end *= mults[i]
    seg_mult = [1]
    for i in range(y - 1, -1, -1):
        if used[i] == 0:
            seg_mult.append(seg_mult[-1] * mults[i])
        else:
            seg_mult.append(seg_mult[-1])
    seg_mult.reverse()
    exc = [seg_mult[0] - v for v in seg_mult]
    init_tot = 0
    for j in range(y + 1):
        if exc[j] != 0:
            init_tot += len(adds[j])
    lo = 0
    hi = 10 ** 18 + 100
    while hi - lo > 1:
        mid = lo + (hi - lo) // 2
        tot = init_tot
        for j in range(y + 1):
            if exc[j] == 0:
                continue
            limit = (mid - 1) // exc[j]
            diff = bisect(adds[j], limit)
            tot -= diff
        if tot >= rem_adds:
            lo = mid
        else:
            hi = mid
    tot = seg_mult[0]
    ct = 0
    for j in range(y + 1):
        tot += pref[j][-1] * seg_mult[j]
        if exc[j] == 0:
            continue
        limit = (lo - 1) // exc[j]
        s_ct = len(adds[j]) - bisect(adds[j], limit)
        tot += pref[j][s_ct] * exc[j]
        ct += s_ct
    if lo != 0:
        assert ct >= rem_adds
        tot -= lo * (ct - rem_adds)
    poss.append(tot * end)
print(max(poss))

Solution (Python):
from bisect import bisect
import sys
input = sys.stdin.readline
(n, b, p, m) = map(int, input().split())
adds = []
curr = []
mults = []
i = 0
for _ in range(n):
    (t, v) = input().split()
    v = int(v)
    if t == '*':
        if v == 1:
            continue
        curr.sort()
        adds.append(curr)
        mults.append(v)
        curr = []
    else:
        curr.append(v)
curr.sort()
adds.append(curr)
pref = []
for l in adds:
    np = [0]
    for v in l[::-1]:
        np.append(v + np[-1])
    pref.append(np)
y = len(mults)
un_m = sorted(set(mults))
z = len(un_m)
ct_m = [0] * z
for v in mults:
    for i in range(z):
        if un_m[i] == v:
            ct_m[i] += 1
from itertools import product
poss = []
assert len(adds) == y + 1
for tup in product(*[range(ct + 1) for ct in ct_m]):
    rem_adds = (b - m * sum(tup)) // p
    if rem_adds < 0:
        continue
    d = {}
    for i in range(z):
        d[un_m[i]] = tup[i]
    end = 1
    used = [0] * y
    for i in range(y):
        if d[mults[i]]:
            used[i] = 1
            d[mults[i]] -= 1
            end *= mults[i]
    seg_mult = [1]
    for i in range(y - 1, -1, -1):
        if used[i] == 0:
            seg_mult.append(seg_mult[-1] * mults[i])
        else:
            seg_mult.append(seg_mult[-1])
    seg_mult.reverse()
    exc = [seg_mult[0] - v for v in seg_mult]
    init_tot = 0
    for j in range(y + 1):
        if exc[j] != 0:
            init_tot += len(adds[j])
    lo = 0
    hi = 10 ** 18 + 100
    while hi - lo > 1:
        mid = lo + (hi - lo) // 2
        tot = init_tot
        for j in range(y + 1):
            if exc[j] == 0:
                continue
            limit = (mid - 1) // exc[j]
            diff = bisect(adds[j], limit)
            tot -= diff
        if tot >= rem_adds:
            lo = mid
        else:
            hi = mid
    tot = seg_mult[0]
    ct = 0
    for j in range(y + 1):
        tot += pref[j][-1] * seg_mult[j]
        if exc[j] == 0:
            continue
        limit = (lo - 1) // exc[j]
        s_ct = len(adds[j]) - bisect(adds[j], limit)
        tot += pref[j][s_ct] * exc[j]
        ct += s_ct
    if lo != 0:
        assert ct >= rem_adds
        tot -= lo * (ct - rem_adds)
    poss.append(tot * end)
print(max(poss))