$n$ 種菜品,$m$ 位廚師。第 $i$ 種菜有 $p_i$ 份訂單。廚師 $j$ 做菜 $i$ 耗時 $t_{i,j}$。每位廚師依次制作分配給他的菜品,每份菜耗時累加。目標:分配菜品給廚師,使總等待時間最小。
若廚師按順序做菜 $d_1, d_2, \dots, d_k$,則第 $x$ 份菜的等待時間 = $\sum_{y=1}^x t_{d_y}$。總等待時間 = $\sum_{x=1}^k (k-x+1) \cdot t_{d_x}$。即越早做的菜權重越大(被後續所有菜疊加)。
跑滿流 $\sum p_i$ 的最小費用即為答案。
全圖邊數 = $m \cdot p_{\text{sum}} \cdot n$。$n\le 40, m\le 100, p_{\text{sum}}\le 800$ → 邊數高達 $3.2 \times 10^6$,SPFA 會 TLE。
對固定廚師 $j$ 和菜品 $i$,費用 $k \cdot t_{i,j}$ 隨槽位 $k$ 遞增。最小費用流會優先使用小 $k$ 的邊。大 $k$ 的邊大概率不會被用到。
實際添加的邊數遠小於全圖,極大加速。
struct Edge { int to, cap, cost, rev; };
vector G[MAXV];
int h[MAXV], dist[MAXV], prevv[MAXV], preve[MAXV];
void addEdge(int from, int to, int cap, int cost) {
G[from].push_back({to, cap, cost, (int)G[to].size()});
G[to].push_back({from, 0, -cost, (int)G[from].size()-1});
}
// 動態加邊:廚師 j 的第 k+1 個槽位
void addNextSlot(int chef, int slot) {
int node = chef * Psum + slot; // Psum = sum(p_i)
for (int dish = 1; dish <= n; ++dish) {
addEdge(node, n*Psum + dish, 1, slot * t[dish][chef]);
}
}
int minCostFlow(int s, int t, int maxf) {
int res = 0;
fill(h, h+V, 0);
while (maxf > 0) {
// SPFA 求最短路
fill(dist, dist+V, INF);
dist[s] = 0;
queue q; q.push(s);
vector inq(V, false); inq[s] = true;
while (!q.empty()) {
int v = q.front(); q.pop(); inq[v] = false;
for (int i=0;i 0 && dist[e.to] > dist[v] + e.cost + h[v] - h[e.to]) {
dist[e.to] = dist[v] + e.cost + h[v] - h[e.to];
prevv[e.to] = v; preve[e.to] = i;
if (!inq[e.to]) { q.push(e.to); inq[e.to] = true; }
}
}
}
if (dist[t] == INF) return -1; // 無法滿流
for (int v=0; v (chef, slot)
int slotNode = v;
int chef = slotNode / Psum;
int slot = slotNode % Psum;
if (slot + 1 <= Psum) addNextSlot(chef, slot + 1);
}
}
}
return res;
}
SCOI 2007「修車」:$n$ 輛車,$m$ 個工位,工位 $j$ 修第 $k$ 輛車耗時 $t_{j,k}$。完全相同模型,同樣需動態加邊優化。
「最小費用流 + 動態加邊」是解決分配位置權重遞增類問題的經典技巧。核心洞見:最優解只使用前綴邊,後綴邊可按需生成。將靜態大圖轉化為動態增長小圖,是競賽編程中「以時換空、以智換力」的典範。