@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. 【maven】maven pom文件详解

    maven pom文件详解 最近配置maven中的pom文件,maven中有些属性不太清楚,在这里记录一下 <project xmlns="http://maven.apache.or ...

  2. fastjson string[]转 json字符串

    @RequestMapping(value = "/friendlink_list/province_list", produces = {"application/js ...

  3. PHP通过sql生成CSV文件并下载,PHP实现文件下载

    /** * PHP通过sql生成CSV文件并下载 * @param string $sql 查询sql,结果为二维数组 * @param array $title 数据,CSV文件标题 * @para ...

  4. Vue2.0史上最全入坑教程(中)—— 脚手架代码详解

    书接上文我们说道,如何利用脚手架(vue-cli)构建一个vue项目,本回书我们一起来学习分析下代码. 回顾下创建后的项目目录:   说明:在*.vue文件,template标签里写html代码,且t ...

  5. python 单元测试之初次尝试

    python 语言中有很多单元测试框架和工具,而unittest单元测试框架作为标准python语言中的一个模块.是其他框架和工具的基础.想要进行单元测试,我们需要使用到unittest框架中的功能. ...

  6. ecshop二次开发之后台秒杀

    1.进入admin->includes->inc_menu.PHP中此文件为定义左侧功能模块超链接 2.添加include/inc_menu.php秒杀管理超链接找链接 $modules[ ...

  7. Lichee ( 四 ) 打包IMAGE

    在<Lichee(三) Android4.0的目标产品文件夹与Lichee的纽带---extract-bsp>中我们分析了extract-bsp的作用和意义.到这里,我们能够開始编译And ...

  8. vue路由history模式刷新页面出现404问题

    vue hash模式下,URL中存在'#',用'history'模式就能解决这个问题.但是history模式会出现刷新页面后,页面出现404.解决的办法是用nginx配置一下.在nginx的配置文件中 ...

  9. DOM事件机制

    前言 本文主要介绍DOM事件级别.DOM事件模型.事件流.事件代理和Event对象常见的应用,希望对你们有些帮助和启发! 本文首发地址为GitHub博客,写文章不易,请多多支持与关注! 一.DOM事件 ...

  10. SDUT-3378_数据结构实验之查找六:顺序查找

    数据结构实验之查找六:顺序查找 Time Limit: 1000 ms Memory Limit: 65536 KiB Problem Description 在一个给定的无序序列里,查找与给定关键字 ...