bzoj2879[Noi2012]美食节 费用流+动态加边

Posted GXZlegend

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了bzoj2879[Noi2012]美食节 费用流+动态加边相关的知识,希望对你有一定的参考价值。

原文地址:http://www.cnblogs.com/GXZlegend


题目描述

CZ市为了欢迎全国各地的同学,特地举办了一场盛大的美食节。作为一个喜欢尝鲜的美食客,小M自然不愿意错过这场盛宴。他很快就尝遍了美食节所有的美食。然而,尝鲜的欲望是难以满足的。尽管所有的菜品都很可口,厨师做菜的速度也很快,小M仍然觉得自己桌上没有已经摆在别人餐桌上的美食是一件无法忍受的事情。于是小M开始研究起了做菜顺序的问题,即安排一个做菜的顺序使得同学们的等待时间最短。小M发现,美食节共有n种不同的菜品。每次点餐,每个同学可以选择其中的一个菜品。总共有m个厨师来制作这些菜品。当所有的同学点餐结束后,菜品的制作任务就会分配给每个厨师。然后每个厨师就会同时开始做菜。厨师们会按照要求的顺序进行制作,并且每次只能制作一人份。此外,小M还发现了另一件有意思的事情: 虽然这m个厨师都会制作全部的n种菜品,但对于同一菜品,不同厨师的制作时间未必相同。他将菜品用1, 2, ..., n依次编号,厨师用1, 2, ..., m依次编号,将第j个厨师制作第i种菜品的时间记为 ti,j 。小M认为:每个同学的等待时间为所有厨师开始做菜起,到自己那份菜品完成为止的时间总长度。换句话说,如果一个同学点的菜是某个厨师做的第k道菜,则他的等待时间就是这个厨师制作前k道菜的时间之和。而总等待时间为所有同学的等待时间之和。现在,小M找到了所有同学的点菜信息: 有 pi 个同学点了第i种菜品(i=1, 2, ..., n)。他想知道的是最小的总等待时间是多少。

输入

输入文件的第1行包含两个正整数n和m,表示菜品的种数和厨师的数量。 第2行包含n个正整数,其中第i个数为pi,表示点第i种菜品的人数。 接下来有n行,每行包含m个非负整数,这n行中的第i行的第j个数为ti,j,表示第j个厨师制作第i种菜品所需的时间。 输入文件中每行相邻的两个数之间均由一个空格隔开,行末均没有多余空格。

输出

输出仅一行包含一个整数,为总等待时间的最小值。

样例输入

3 2
3 1 1
5 7
3 6
8 9

样例输出

47


题解

动态加边+费用流

这道题和 修车 类似,然而数据范围大了若干倍,直接做会T。

于是可以动态加边,当且仅当第i名厨师的倒数第j道菜做完,才加点(i,j+1)和对应的边。

如此高端。。。

具体方法:

1.拆m名厨师为c*m个,其中c=∑pi,并编号为厨师(i,j),表示做应做的倒数第i道菜的第j名厨师。将编号转化为数字为(i-1)*m+j(转化方法和网上一些题解不同,其实没什么区别)。

2.连S->(i,j),容量为1,费用为0(其实也可以一样动态连边,不过优化作用不大);

   连k+c*m->T,容量为p[k],费用为0.

3.连(1,j)->k+c*m,容量为1,费用为time[k][j]。

4.跑费用流,同时找到T->S路径上from值为S的点,设为厨师(x,y)。跑完费用流以后,加边(x+1,y)->k+c*m,容量为1,费用为time[k][j]*(x+1)。

最后的最小费用就是答案。

#include <cstdio>
#include <cstring>
#include <queue>
#define N 100010
#define M 3000010
using namespace std;
queue<int> q;
int n , m , c , p[50] , d[50][110] , head[N] , to[M] , val[M] , cost[M] , next[M] , cnt = 1 , s , t , dis[N] , inq[N] , from[N] , pre[N];
void add(int x , int y , int v , int c)
{
	to[++cnt] = y , val[cnt] = v , cost[cnt] = c , next[cnt] = head[x] , head[x] = cnt;
	to[++cnt] = x , val[cnt] = 0 , cost[cnt] = -c , next[cnt] = head[y] , head[y] = cnt;
}
bool spfa()
{
	int x , i;
	memset(dis , 0x3f , sizeof(dis));
	memset(from , -1 , sizeof(from));
	dis[s] = 0 , q.push(s);
	while(!q.empty())
	{
		x = q.front() , q.pop() , inq[x] = 0;
		for(i = head[x] ; i ; i = next[i])
		{
			if(val[i] && dis[to[i]] > dis[x] + cost[i])
			{
				dis[to[i]] = dis[x] + cost[i] , from[to[i]] = x , pre[to[i]] = i;
				if(!inq[to[i]]) inq[to[i]] = 1 , q.push(to[i]);
			}
		}
	}
	return ~from[t];
}
int mincost()
{
	int ans = 0 , i , k , x , y;
	while(spfa())
	{
		k = 0x3f3f3f3f;
		for(i = t ; i != s ; i = from[i])
		{
			k = min(k , val[pre[i]]);
			if(from[i] == s) x = (i - 1) / m + 1 , y = (i - 1) % m + 1;
		}
		ans += k * dis[t];
		for(i = t ; i != s ; i = from[i]) val[pre[i]] -= k , val[pre[i] ^ 1] += k;
		for(i = 1 ; i <= n ; i ++ ) add(m * x + y , c * m + i , 1 , d[i][y] * (x + 1));
	}
	return ans;
}
int main()
{
	int i , j;
	scanf("%d%d" , &n , &m);
	s = 0;
	for(i = 1 ; i <= n ; i ++ ) scanf("%d" , &p[i]) , c += p[i];
	t = c * m + n + 1;
	for(i = 1 ; i <= n ; i ++ )
		for(j = 1 ; j <= m ; j ++ )
			scanf("%d" , &d[i][j]);
	for(i = 1 ; i <= c * m ; i ++ ) add(s , i , 1 , 0);
	for(i = 1 ; i <= n ; i ++ ) add(c * m + i , t , p[i] , 0);
	for(i = 1 ; i <= m ; i ++ )
		for(j = 1 ; j <= n ; j ++ )
			add(i , c * m + j , 1 , d[j][i]);
	printf("%d\\n" , mincost());
	return 0;
}

 

以上是关于bzoj2879[Noi2012]美食节 费用流+动态加边的主要内容,如果未能解决你的问题,请参考以下文章

[BZOJ2879] [Noi2012] 美食节 (费用流 & 动态加边)

BZOJ-2879美食节 最小费用最大流 + 动态建图

BZOJ2879: [Noi2012]美食节

bzoj2879: [Noi2012]美食节

C++之路进阶——最小费用最大流(善意的投票)

费用流BZOJ1061[NOI2008]-志愿者招募