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
= map(int, input().split())
n, t, r = list(map(int, input().split()))
robotnici # robotníkov zoradíme od najzručnejších (takých, ktorých školenie bude trvať najkratšie) vzostupne
robotnici.sort()
= [0] * (r + 1)
suma # 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):
+ 1] = suma[i] + robotnici[i]
suma[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;
long long> robotnici(r + 1);
vector<long long> suma(r + 1);
vector<
for (i = 0; i < r; i++)
1];
cin >> robotnici[i +
// 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ť.