Zadanie

Po výskyte ploštíc na matfyze vedeniu došla trpezlivosť. Rozhodlo sa urobiť jeho kompletnú prerábku.

Gratulujeme! Tvoja stavebná firma Kopu Stavieb Postavím vyhrala verejné obstarávanie a teraz má zabezpečiť prerábanie matfyzu. Prerábka pozostáva z úloh, pričom o každej vieš ako dlho trvá jej dokončenie. K dispozícii máš stavbyvedúceho, zároveň si môžeš najať niekoľko robotníkov. Predtým, ako sa robotník zapojí do práce, musí absolvovať školenie u stavbyvedúceho, ktorý počas toho nepracuje. Bez stavbyvedúceho na stavbe sa robotníci flákajú a nepracujú. Ako rýchlo dokážeš vykonať prerábku?

Úloha

Úlohou je nájsť najrýchlejší možný čas za aký sa dá stihnúť vykonať \(n\) úloh, pričom každá trvá \(t\) hodín. Na začiatku máš iba stavbyvedúceho, ale na začiatku každej hodiny si môžeš najať najviac \(r\) robotníkov. Robotníci musia absolvovať povinné školenie u stavbyvedúceho, ktorý počas tej doby nepracuje. Každý robotník je inak šikovný a trvá mu iný čas, kým bude zaškolený. Stavbyvedúci dokáže školiť jedného robotníka naraz. Bez stavbyvedúceho sa nepracuje. Po absolvovaní školenia dokáže robotník pracovať rovnako rýchlo ako stavbyvedúci. Na každej úlohe môže pracovať najviac 1 človek (či už robotník, alebo stavbyvedúci).

Formát vstupu

Na vstupe je jediný riadok, na ktorom sú medzerou oddelené čísla \(n,t,r\;(1\leq t\leq 5\,000)\) – počet úloh, trvanie jednej úlohy, a počet robotníkov, ktoré máš k dispozícii. Na druhom riadku je \(r\) medzerou oddelených čísel \(p_i; (1\leq p\leq 100\,000)\) – čas, koľko trvá zaškoliť \(i\)-teho robotníka. Platí, že:

  • v prvej sade platí, že školenie aj vykonanie úlohy trvá vždy 1 (\(\forall i: p_i=t=1\))
  • v druhej a tretej sade platí, že školenie trvá vždy rovnako ako vykonanie úlohy (\(\forall i: p_i=t\))
  • o zvyšných piatich sadách nemôžete nič predpokladať

Pre premenné platia nasledujúce obmedzenia:

Sada 1 2 3 4
\(1 \leq n \leq\) \(100\) \(10000\) \(100000\) \(300000\)
\(1 \leq r \leq\) \(100\) \(5000\) \(200000\) \(1000000\)

Formát výstupu

Vypíš jeden riadok a v ňom jedno celé číslo, najkratší možný počet hodín za ktorý sa dá stihnúť prerábka.

Príklady

Input:

2 2 3
3 2 1

Output:

3

Samotný stavbyvedúci by prácu vykonal za \(2\cdot 2=4\) hodiny, ale viac sa oplatí, ak hneď na začiatku za 1 hodinu zaškolí jedného (tretieho) robotníka, a potom tento robotník spolu s ním urobí každý jednu 2 hodiny trvajúcu úlohu. Celkovo teda stavba bude trvať len \(2+1=3\).

Input:

1 2 3
1 2 3

Output:

2

Pri jednej úlohe sa neoplatí najímať robotníka.

Input:

3 3 3
50 50 50

Output:

9

Školenie robotníka by trvalo tak dlho, že sa viac oplatí robiť všetko sám.

Úlohou bolo nájsť najrýchlejší možný čas, za ktorý sa dalo splniť \(n\) úloh, pričom splnenie každej trvá \(t\) hodín. Plnenie úloh bolo možné urýchliť (ale aj spomaliť) vyškolením a najatím najviac \(r\) robotníkov, pričom každý má samostatne určenú zručnosť.

Bruteforce

Platí, že jediné spôsoby, akými môžeme ovplyvniť čas splnenia úloh sú:

  • počet robotníkov, ktorých najmeme, označme si ho \(i\), pričom \(0\leq i\leq r\) - to je \(r\) možností
  • ktorých konkrétnych \(i\) robotníkov z \(r\) si vyberieme - to je \(r \choose i\) možností
  • po splnení ktorej úlohy každého z nich vyškolíme - to je \(r^n\) možností.

Čas splnenia \(n\) úloh, pričom každá trvá \(t\) hodín s \(i\) robotníkmi vieme vypočítať v konštantnom čase vzorcom \(\lceil\frac{n}{i}\rceil\cdot t+u\), kde \(u\) je doba školenia daných robotníkov. Pomocou bruteforce teda vyskúšame nájsť čas splnenia úloh pre každú kombináciu týchto faktorov. Pamäťová zložitosť takéhoto riešenia je \(O(r)\), keďže si pamätáme len zručnosť každého robotníka. Časová zložitosť takéhoto riešenia je \(O(r^n)\).

Vlastnosti optimálneho riešenia

Poďme za pozrieť na spôsoby, akými vieme eliminovať niektoré z vyššie opísaných faktorov.

Kedy vyškoliť robotníkov

Zamyslime sa nad tým, kedy sa najviac oplatí školiť robotníkov. Predpokladajme, že už sme našli optimálny počet aj výber konkrétnych robotníkov, ktorých zamestnáme a stačí nájsť už len optimálne úlohy, po ktorých splnení jednotlivých robotníkov vyškolíme. Zrejme platí, že to bude vždy ešte pred začatím stavby, keď nie je splnená ani jedna úloha. Keďže školenie robotníka trvá rovnako dlho bez ohľadu na to, kedy sa začne, nikdy sa neoplatí školenie nejakých robotníkov odkladať, lebo sa tým zbytočne stratí čas, počas ktorého mohli títo robotníci pracovať, ale nepracujú.

Ktorých robotníkov si vybrať

Teraz predpokladajme, že už máme optimálny počet robotníkov a aj sme určili správny moment, kedy ich vyškoliť (už vieme, že to je na začiatku). Môže nastať prípad, kedy je optimálny počet robotníkov menší ako \(r\), čiže si môžeme vyberať, ktorých konkrétnych najmeme. V tomto prípade bude zrejme najoptimálnejšie najať tých najzručnejších, lebo ich školenie zaberie najkratší čas.

Koľko robotníkov vyškoliť

Keďže sme už určili, kedy sa najviac oplatí vyškoliť robotníkov aj ktorých konkrétnych si vybrať, stačí nám už len nájsť optimálny počet robotníkov, ktorý vyškolíme. Nakoľko čas splnenia \(n\) úloh s \(i\) robotníkmi ak každá trvá \(t\) dlho vieme vypočítať v konštantnom čase vzorcom z predošlého riešenia, môžeme si dovoliť vyskúšať všetky možné počty robotníkov, ktoré zaškoliť. Uvedomme si, že ak by sme aj mali možnosť vyškolit viac robotníkov ako je úloh, zrejme sa to nikdy neoplatí, a teda počet robotníkov ktorý sa oplatí vyškolit je zhora ohraničený počtom úloh.

Počet možností bude preto vždy najviac \(\text{min}(r, n)\).

Riešenie

Zhrňme si, ako bude vyzerať nájdenie optimálneho riešenia. Nemusíme skúšať všetky možné úlohy, po ktorých vyškoliť robotníkov, ale ich všetkých vyškoliť hneď zo začiatku. Zároveň nemusíme skúšať všetky možné kombinácie jednotlivých robotníkov, ale vybrať vždy len tých najzručnejších. Zoradenie robotníkov od najzručnejších zaberie \(O(r\log r)\). Tým pádom stačí nájsť len optimálny počet robotníkov. Tento počet nájdeme vyskúšaním všetkých možností. Teda, pre každý počet robotníkov od \(0\) po \(r\) vypočítame, ako dlho bude trvať splnenie úloh a vypíšeme najmenšiu nájdenú hodnotu, čo bude trvať O(r). Pamäťová zložitosť takéhoto riešenia je \(O(r)\), keďže si pamätáme len zručnosť každého robotníka. Časová zložitosť takéhoto riešenia je \(O(r\log r)\).

from math import ceil


def time(n: int, t: int, i: int) -> int:
    """
    Výpočet času do splnenia výstavby.
    :param n: počet úloh
    :param t: počet hodín, ktorý trvá jedna úloha
    :param i: počet robotníkov (bez stavbyvedúceho), ktorí budú robiť úlohy
    :return: Počet hodín, ktorý trvá splnenie n úloh, pričom každá trvá t hodín, \
     plus počet hodín strávený školením i robotníkov
    """
    return suma[i] + ceil(n / (i + 1)) * t


n, t, r = map(int, input().split())
robotnici = list(map(int, input().split()))
robotnici.sort()  # robotníkov zoradíme od najzručnejších (takých, ktorých školenie bude trvať najkratšie) vzostupne

suma = [0] * (r + 1)
# pred for cyklom: suma[i] = 0, 0 <= i <= r
# po for cykle: suma[i] = počet hodín, ktorý trvá zaškolenie i najzručnejších robotníkov
for i in range(r):
    suma[i + 1] = suma[i] + robotnici[i]
print(min(time(n, t, i) for i in range(0, min(r + 1, n))))
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<numeric>

using namespace std;

/**
 * Výpočet času do splnenia výstavby.
 * @param n počet úloh
 * @param t počet hodín, ktorý trvá jedna úloha
 * @param i počet robotníkov (bez stavbyvedúceho), ktorí budú robiť úlohy
 * @param suma časy školení robotníkov
 * @return Počet hodín, ktorý trvá splnenie n úloh, pričom každá trvá t hodín,
     plus počet hodín strávený školením i robotníkov.
 */
long long cas(long long n, long long t, long long i, vector<long long> &suma) {
    return suma[i] + (long long) (ceil((double) n / (double) (i + 1))) * t;
}

int main() {
    long long i, n, t, r;
    cin >> n >> t >> r;

    vector<long long> robotnici(r + 1);
    vector<long long> suma(r + 1);

    for (i = 0; i < r; i++)
        cin >> robotnici[i + 1];

    // robotníkov zoradíme od najzručnejších (takých, ktorých školenie bude trvať najkratšie) vzostupne
    sort(robotnici.begin(), robotnici.end());
    // suma[i] = počet hodín, ktorý trvá zaškolenie i najzručnejších robotníkov
    partial_sum(robotnici.begin(), robotnici.end(), suma.begin());

    long long res = n * t;
    for (i = 0; i < min(r + 1, n + 1); i++) {
        res = min(res, cas(n, t, i, suma));
    }

    cout << res << endl;
}

Diskusia

Tu môžte voľne diskutovať o riešení, deliť sa o svoje kusy kódu a podobne.

Pre pridávanie komentárov sa musíš prihlásiť.