@description@
CZ 市为了欢迎全国各地的同学,特地举办了一场盛大的美食节。
作为一个喜欢尝鲜的美食客,小 M 自然不愿意错过这场盛宴。他很快就尝遍了美食节所有的美食。然而,尝鲜的欲望是难以满足的。尽管所有的菜品都很可口,厨师做菜的速度也很快,小 M 仍然觉得自己桌上没有已经摆在别人餐桌上的美食是一件无法忍受的事情。于是小 M 开始研究起了做菜顺序的问题,即安排一个做菜的顺序使得同学们的等待时间最短。
小 M 发现,美食节共有 n 种不同的菜品。每次点餐,每个同学可以选择其中的一个菜品。总共有 m 个厨师来制作这些菜品。当所有的同学点餐结束后,菜品的制作任务就会分配给每个厨师。然后每个厨师就会同时开始做菜。厨师们会按照要求的顺序进行制作,并且每次只能制作一人份。
此外,小 M 还发现了另一件有意思的事情——虽然这 m 个厨师都会制作全部的 n 种菜品,但对于同一菜品,不同厨师的制作时间未必相同。他将菜品用 1, 2, ..., n 依次编号,厨师用 1, 2, ..., m 依次编号,将第 j 个厨师制作第 i 种菜品的时间记为 tij。
小 M 认为:每个同学的等待时间为所有厨师开始做菜起,到自己那份菜品完成为止的时间总长度。换句话说,如果一个同学点的菜是某个厨师做的第 k 道菜,则他的等待时间就是这个厨师制作前 k 道菜的时间之和。而总等待时间为所有同学的等待时间之和。
现在,小 M 找到了所有同学的点菜信息——有 pi 个同学点了第 i 种菜品(i = 1, 2, ..., n)。他想知道的是最小的总等待时间是多少。
输入格式
输入文件的第 1 行包含两个正整数 n 和 m,表示菜品的种数和厨师的数量。
第 2 行包含 n 个正整数,其中第 i 个数为 pi,表示点第 i 种菜品的人数。
接下来有 n 行,每行包含 m 个非负整数,这 n 行中的第 i 行的第 j 个数为 tij,表示第 j 个厨师制作第 i 种菜品所需的时间。
输入中每行相邻的两个数之间均由一个空格隔开,行末均没有多余空格。
输出格式
输出仅一行包含一个整数,为总等待时间的最小值。
样例
样例输入
3 2
3 1 1
5 7
3 6
8 9
样例输出
47
数据范围与提示
n <= 40, m <= 100, ∑p <= 800, tij <= 1000。
@solution@
每个厨师会选择一些菜品制作,可以看作厨师与菜品的匹配,联想到网络流。
把菜品看作流量,等待时间看作费用。如果可以通过某种建图使得第 i 个菜品对应的流量 = 容量 = pi,在此基础上费用最少,就可以直接跑最小费用最大流求解。
假如一个厨师先后做了等待时间为 t1, t2, ..., tk 的菜品,则这个厨师对应的总等待时间为 t1 + (t1 + t2) + ... , (t1 + t2 + ... + tk) = k*t1 + (k-1)*t2 + ... + 1*tk。
看起来很有规律,但是这个代价与 k 有关,当 k 是个不确定的量时不好求解。我们不妨换种定义,假如一个厨师按照从后到前的顺序依次做了等待时间为 t1', t2', ..., tk',那么这个厨师对应的总等待时间为 t1'*1 + t2'*2 + ... + tk'*k。这样每一项就与 k 无关了。
但是始终还有一个系数,无法直接搬到费用流上面去。不妨考虑大胆拆点,将第 j 个厨师拆成 ∑p 个点,第 (j, i) 个点表示第 j 个厨师做倒数第 i 道菜品。这样系数的问题就解决了。
然后源点 s 向 m 个厨师的 ∑p 个点连容量为 1,费用为 0 的边;所有的 n 种菜品向汇点 t 连容量为 pi,费用为 0 的边;第 j 个厨师的第 k 个点向第 i 中菜品连容量为 1,费用为 k * tij 的边。
跑最小费用最大流即可。
看起来非常完美。但实际上,即使网络流的玄学复杂度也跑不过这道题的数据。
解决方法是,注意到增广时总沿着最短路增广,而第 j 个厨师的第 k 个点连出去的最短路总是比第 j 个厨师的第 k+1 个点连出去的最短路要更短。
于是我们当且仅当第 k 个点增广过后,才把第 k+1 个点的相关边加入流网络中。
这样为什么会快很多?很简单嘛。你虽然拆出来 m*∑p 个点,但实际上有用的也不过 ∑p 个点,所以我们每一次增广的图的点数就从理论的 m*∑p 坍缩成 ∑p。而 m*∑p 与 ∑p 根本也不是一个量级的嘛。
@accepted code@
#include<cstdio> #include<algorithm> using namespace std; const int MAXN = 40; const int MAXM = 100; const int MAXP = 800; const int MAXV = MAXP*MAXM + MAXN + 5; const int MAXE = 20*MAXV + 5; const int INF = (1<<30); struct FlowGraph{ struct edge{ int to, cap, flow, cost; edge *nxt, *rev; }edges[MAXE + 5], *adj[MAXV + 5], *cur[MAXV + 5], *ecnt; FlowGraph() {ecnt = &edges[0];} int s, t, n; void addedge(int u, int v, int c, int w) { edge *p = (++ecnt), *q = (++ecnt); p->to = v, p->cap = c, p->flow = 0, p->cost = w; p->nxt = adj[u], adj[u] = p; q->to = u, q->cap = 0, q->flow = 0, q->cost = -w; q->nxt = adj[v], adj[v] = q; p->rev = q, q->rev = p; } int hp[MAXV + 5], f[MAXV + 5]; void update(int x, int k) { f[x] = k; while( x ) { hp[x] = x; if( (x<<1) <= n && f[hp[x]] > f[hp[x<<1]] ) hp[x] = hp[x<<1]; if( (x<<1|1) <= n && f[hp[x]] > f[hp[x<<1|1]] ) hp[x] = hp[x<<1|1]; x >>= 1; } } int d[MAXV + 5], h[MAXV + 5]; bool relabel() { for(int i=1;i<=n;i++) h[i] += d[i], d[i] = f[i] = INF, hp[i] = i, cur[i] = adj[i]; update(s, d[s] = 0); while( f[hp[1]] != INF ) { int x = hp[1]; update(x, INF); for(edge *p=adj[x];p;p=p->nxt) { int w = p->cost + h[x] - h[p->to]; if( p->cap > p->flow && d[x] + w < d[p->to] ) update(p->to, d[p->to] = d[x] + w); } } return !(d[t] == INF); } bool vis[MAXV + 5]; int aug(int x, int tot) { if( x == t ) return tot; int sum = 0; vis[x] = true; for(edge *&p=cur[x];p;p=p->nxt) { int w = p->cost + h[x] - h[p->to]; if( p->cap > p->flow && !vis[p->to] && d[x] + w == d[p->to] ) { int del = aug(p->to, min(tot - sum, p->cap - p->flow)); p->flow += del, p->rev->flow -= del, sum += del; if( sum == tot ) break; } } vis[x] = false; return sum; } }G; int T[MAXN + 5][MAXM + 5], p[MAXN + 5], n, m; FlowGraph::edge *e[MAXM + 5]; int id[MAXM + 5], cnt[MAXM + 5]; int main() { scanf("%d%d", &n, &m); G.s = n + 1, G.t = G.n = n + 2; for(int i=1;i<=n;i++) { scanf("%d", &p[i]); G.addedge(G.s, i, p[i], 0); } for(int j=1;j<=m;j++) id[j] = (++G.n), G.addedge(id[j], G.t, 1, 0), e[j] = G.ecnt, cnt[j] = 1; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { scanf("%d", &T[i][j]); G.addedge(i, id[j], 1, cnt[j]*T[i][j]); } int ans = 0; while( G.relabel() ) { int del = G.aug(G.s, INF); ans += del*(G.d[G.t] + G.h[G.t]); for(int j=1;j<=m;j++) if( e[j]->flow ) { id[j] = (++G.n), G.addedge(id[j], G.t, 1, 0), e[j] = G.ecnt, cnt[j]++; for(int i=1;i<=n;i++) G.addedge(i, id[j], 1, cnt[j]*T[i][j]); } } printf("%d\n", ans); }
@details@
做完这道题你就可以继续去做SCOI的修车了。