bzoj2879(费用流+动态加边)

本文介绍了一种算法问题,旨在通过合理分配菜品制作任务给不同的厨师以实现最小化总等待时间。采用动态加边的方法优化网络流算法,确保计算效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

小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,费用为t[i][j]×k 的边(k表示倒数第几个做,因为倒数第一做只有这个人需要等,做这个菜的时间不会其他菜) ,但是我们发现其实这些边最后一定有很多是不会走到的。

所以刚开始,把所有菜和每一个厨师倒数第一个点相连

在增广过程中,在增光路上找到和原点相连的点,并计算出他是哪一个厨师做的倒数第几个菜,然后把所有菜向这个厨师下一个菜加边


如果不动态加边就会TLE,但是本题的关键还是建图是很经典的

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#include<queue>
using namespace std;
const int N=100005;
const int inf=0x3f3f3f3f;
int s,t,n,m,p[50],total;
int head[N],dis[N],pre[N],now[N],tot,id[105][805],cost[105][105],h[1005];
bool b[N];
struct aa
{
	int flow,cap,to,pre,w;
}edge[N*100];
void addedge(int x,int y,int z,int w)
{
	edge[++tot].to=y;edge[tot].cap=z;edge[tot].w=w;edge[tot].pre=head[x];head[x]=tot;
	edge[++tot].to=x;edge[tot].cap=0;edge[tot].w=-w;edge[tot].pre=head[y];head[y]=tot;
}
bool spfa()
{
	memset(dis,inf,sizeof(dis));
	memset(b,false,sizeof(b));
	dis[s]=0;queue<int> q;q.push(s);
	while (!q.empty())
	{
		int u=q.front();q.pop();
		for (int i=head[u];i;i=edge[i].pre)
		if (edge[i].cap>edge[i].flow&&dis[edge[i].to]>dis[u]+edge[i].w)
		{
			dis[edge[i].to]=dis[u]+edge[i].w;
			pre[edge[i].to]=u;now[edge[i].to]=i;
			if (!b[edge[i].to])
			{
				b[edge[i].to]=true;
				q.push(edge[i].to);
			}
		}
		b[u]=false;
	}
	return dis[t]!=inf;
}
int work()
{
	int ans=0,a,b,y;
	while (spfa())
	{
		int flow=inf;
		for (int i=t;i!=s;i=pre[i])
		{
			flow=min(flow,edge[now[i]].cap-edge[now[i]].flow);
			if (pre[i]==s) y=i;
		}
		ans+=dis[t]*flow;
		for (int i=t;i!=s;i=pre[i])
		edge[now[i]].flow+=flow,edge[((now[i]-1)^1)+1].flow-=flow;
		
		a=(y-n-1)/total+1;b=(y-n)%total+1;
		for (int i=1;i<=n;i++)
	 	addedge(id[a][b],i,1,b*cost[a][i]);
	}
	return ans;
}
int main()
{
	scanf("%d%d",&n,&m);
	int x,bj=n;
	s=0;
	for (int i=1;i<=n;i++) scanf("%d",&h[i]),total+=h[i];
	for (int i=1;i<=m;i++)
		for (int j=1;j<=total;j++) id[i][j]=++bj,addedge(s,id[i][j],1,0);
	t=bj+1;
	for (int i=1;i<=n;i++) addedge(i,t,h[i],0);
	for (int i=1;i<=n;i++)
		for (int j=1;j<=m;j++) 
		{
			scanf("%d",&cost[j][i]);
			addedge(id[j][1],i,1,cost[j][i]);
		}
	printf("%d",work());
	return 0;
}

总结

1:逆向思维并不简单,但是很多时候思维的转化尤其重要。这道题我觉得最为巧妙的是,厨师做的第几个菜进行分点,但是如果由此进行连边,每一个菜和厨师做的第几个菜连边分别连边的话,我们无法计算最小的费用(等待时间),因为我们无法确定之前做的菜是哪些?这里就引出了本题中一种非常关键的转化:第k个菜的等待时间是之前所有等待时间之和,所求的总时间就是所有等待时间之和,我们这样想,来想每一个点对于总时间的贡献,最后一个菜的贡献是time*1.,倒数第二个菜:time*2,……所以每一个厨师做的菜的时间就可以通过这种方式转化,每个点都与不同的菜连容量为1,费用为t[i][j]×k 的边(这里的k其实表示的倒数第k个菜)。因为肯定是先连k小的点,如果再来一个点就会占两道菜了,因为有增广路所以可以保证会反悔,新的两个菜会重新排顺序,使费用最小

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值