@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的修车了。

@loj - 2674@ 「NOI2012」美食节的更多相关文章

  1. 【LOJ】#2674. 「NOI2012」美食节

    题解 这道题的费用流如果朴素一点怎么建边呢 建出\(\sum_{i = 1}^{n} p^{i} M\)个点,第\(i\)个厨师的第\(j\)个点表示这个厨师倒数第\(j\)个做的是某道菜 这个点向汇 ...

  2. Loj #2192. 「SHOI2014」概率充电器

    Loj #2192. 「SHOI2014」概率充电器 题目描述 著名的电子产品品牌 SHOI 刚刚发布了引领世界潮流的下一代电子产品--概率充电器: 「采用全新纳米级加工技术,实现元件与导线能否通电完 ...

  3. Loj #3096. 「SNOI2019」数论

    Loj #3096. 「SNOI2019」数论 题目描述 给出正整数 \(P, Q, T\),大小为 \(n\) 的整数集 \(A\) 和大小为 \(m\) 的整数集 \(B\),请你求出: \[ \ ...

  4. Loj #3093. 「BJOI2019」光线

    Loj #3093. 「BJOI2019」光线 题目描述 当一束光打到一层玻璃上时,有一定比例的光会穿过这层玻璃,一定比例的光会被反射回去,剩下的光被玻璃吸收. 设对于任意 \(x\),有 \(x\t ...

  5. Loj #3089. 「BJOI2019」奥术神杖

    Loj #3089. 「BJOI2019」奥术神杖 题目描述 Bezorath 大陆抵抗地灾军团入侵的战争进入了僵持的阶段,世世代代生活在 Bezorath 这片大陆的精灵们开始寻找远古时代诸神遗留的 ...

  6. Loj #2542. 「PKUWC2018」随机游走

    Loj #2542. 「PKUWC2018」随机游走 题目描述 给定一棵 \(n\) 个结点的树,你从点 \(x\) 出发,每次等概率随机选择一条与所在点相邻的边走过去. 有 \(Q\) 次询问,每次 ...

  7. Loj #3059. 「HNOI2019」序列

    Loj #3059. 「HNOI2019」序列 给定一个长度为 \(n\) 的序列 \(A_1, \ldots , A_n\),以及 \(m\) 个操作,每个操作将一个 \(A_i\) 修改为 \(k ...

  8. Loj #3056. 「HNOI2019」多边形

    Loj #3056. 「HNOI2019」多边形 小 R 与小 W 在玩游戏. 他们有一个边数为 \(n\) 的凸多边形,其顶点沿逆时针方向标号依次为 \(1,2,3, \ldots , n\).最开 ...

  9. Loj #3055. 「HNOI2019」JOJO

    Loj #3055. 「HNOI2019」JOJO JOJO 的奇幻冒险是一部非常火的漫画.漫画中的男主角经常喜欢连续喊很多的「欧拉」或者「木大」. 为了防止字太多挡住漫画内容,现在打算在新的漫画中用 ...

随机推荐

  1. Python实例 类和继承

    class Base:     def __init__(self):         self.data = []     def add(self, x):         self.data.a ...

  2. Leetcode40. Combination Sum组合总数2 II

    给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合. candidates 中的每个数字在每个组合中只能使用一次. ...

  3. thinkcmf 导航高亮制作方法(适用于多级导航)(通用)

    平时用thinkcmf网站开发经常需要导航点击之后高亮,就写了一些实现方法分享一下. 思路很简单,先获取当前页面的顶级栏目的地址,然后与导航中的地址比较,相同的就加上一个class,把下面函数理解了不 ...

  4. ubuntu16安装python3.7

    ####################################################源码安装python,注意shell脚本第一行开头的要求#################### ...

  5. Poj 2796 单调栈

    关于单调栈的性质,和单调队列基本相同,只不过单调栈只使用数组的尾部, 类似于栈. Accepted Code: /******************************************* ...

  6. hdu 2444 The Accomodation of Students(二分匹配 匈牙利算法 邻接表实现)

    The Accomodation of Students Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K ( ...

  7. ORACLE 使用笔记

    ORACLE TRUNC()函数 TRUNC():类似截取函数,按指定的格式截取输入的数据. 1.[trunc(for dates)]TRUNC()函数处理日期 语法格式:TRUNC(date[,fm ...

  8. Amazon Redshift数据迁移到MaxCompute

    Amazon Redshift数据迁移到MaxCompute Amazon Redshift 中的数据迁移到MaxCompute中经常需要先卸载到S3中,再到阿里云对象存储OSS中,大数据计算服务Ma ...

  9. POJ 2078

    16ms 解法: #include <cstdio> //using namespace std; ][]; ][]; ]; ]; int n,min,max; void solve(in ...

  10. 廖雪峰Python总结2

    1.切片 L[0:3]表示,从索引0开始,直到索引3为止,但是不包括索引3.如果第一个索引是0,还可以省略L[:3] 倒数切片:L[-n:-1],-1是倒数第一个元素,L[-n:-1]不包括倒数第一个 ...