题目链接:
https://www.luogu.com.cn/problem/P2050
思路来源博客:
https://www.luogu.com.cn/blog/litble-blog/ti-xie-p2050-mei-si-jie-fei-yong-liu
这个博客真心很棒,建议复习再看
算法:1:最小费用最大流 Dinic+Spfa
思路:
1:这题就是P2053修车https://blog.csdn.net/aiwo1376301646/article/details/104313451的数据加强版,那么建图方式也差不多
2:对于每个菜建立一个点,源点向其连一条流量为需求量费用为0的边
3:然后再建一层点,分别表示第j个厨师做第倒数i道菜。向汇点连一条流量为1费用为0的边
4:把每一个厨师拆为(一共要做的菜的总盘数sum个)而不是菜的种类n个,所以一共有m*sum个厨师节点
5:假设有一个点表示第j个厨师做第倒数k道菜,那么对于菜i,向其连一条流量为1,费用为k×a(i,j)的边。这表示第j个厨师做的倒数第k道菜是菜i,那么就要做a(i,j)这么长的时间,有k个人要等这么长的时间
6:意会一下可以发现,这个模型能解决“同时做”问题
样例图解:
优化:
1:由于此题数据量很大,把所有边连完后再跑费用流是一定会TLE的(60分)
2:由于我们跑一次spfa只能找出一次增广路,所以我们可以暂时不连不需要的边。一开始,我们把所有厨师做倒数第1道菜与所有菜连好,然后找一条增广路,这条增广路上一定经过了一个点,表示第j个厨师做倒数第1道菜,于是我们添加点(第j个厨师做倒数第2道菜),与汇点和所有菜连边,以此类推
3:意会一下可以发现,这样每次spfa的时候,需要的边都被连上了
图解:
1:第一步(一开始)红色线
2:第一步之后 添加第二个蓝点,需要添加的边(绿色)
代码:
一:加边函数addedge()没有一次就把正反边都包括,这样不如一次就包括省事
#include <bits/stdc++.h>
using namespace std;
const int maxn=8e4+4e1+2,maxm=6e6+5e5+8e1+1;
int m,n,s,t,a[41][101],tot=1,head[maxn],dis[maxn],flow[maxn],pre[maxn],last[maxn],maxflow,mincost,p[41],sum;
int cook[maxn],dish[maxn];
bool vis[maxn];
queue<int>q;
struct edge
{
int to,next,w,dis;
}e[maxm];
void addedge(int x,int y,int w,int dis)
{
e[++tot].to=y;e[tot].w=w;e[tot].dis=dis;e[tot].next=head[x];head[x]=tot;
}
bool spfa()
{
memset(dis,0x7f,sizeof(dis));
memset(flow,0x7f,sizeof(flow));
memset(vis,0,sizeof(vis));
q.push(s);vis[s]=1;dis[s]=0;pre[t]=-1;
while(!q.empty())
{
int now=q.front();q.pop();
vis[now]=0;
for(int i=head[now];i;i=e[i].next)
{
int y=e[i].to;
if(e[i].w>0&&dis[y]>dis[now]+e[i].dis)
{
dis[y]=dis[now]+e[i].dis;
pre[y]=now;
last[y]=i;
flow[y]=min(flow[now],e[i].w);
if(!vis[y])
{
q.push(y);
vis[y]=1;
}
}
}
}
return pre[t]!=-1;
}
void dfs()
{
int now=t;
maxflow+=flow[t];
mincost+=flow[t]*dis[t];
while(now!=s)
{
e[last[now]].w-=flow[t];
e[last[now]^1].w+=flow[t];
now=pre[now];
}
}
int main()
{
ios::sync_with_stdio(0);
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&p[i]),sum+=p[i];
s=0,t=m*sum+n+1;
for(int i=1;i<=n;i++)addedge(s,i+sum*m,p[i],0),addedge(i+sum*m,s,0,0);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
scanf("%d",&a[i][j]);
addedge(i+sum*m,sum*(j-1)+1,1,a[i][j]),addedge(sum*(j-1)+1,i+sum*m,0,-a[i][j]);
}
for(int i=1;i<=m;i++)addedge((i-1)*sum+1,t,1,0),addedge(t,(i-1)*sum+1,0,0);
for(int i=1;i<=m;i++)
for(int j=1;j<=sum;j++)
{
int temp=(i-1)*sum+j;
dish[temp]=j,cook[temp]=i;
}
while(spfa())
{
dfs();
int temp=e[last[t]^1].to;
addedge(temp+1,t,1,0),addedge(t,temp+1,0,0);
for(int i=1;i<=n;i++)
addedge(i+m*sum,temp+1,1,a[i][cook[temp+1]]*dish[temp+1]),
addedge(temp+1,i+m*sum,0,-a[i][cook[temp+1]]*dish[temp+1]);
}
printf("%d\n",mincost);
return 0;
}
二:加边函数addedge()一次就把正反边都包括
#include <bits/stdc++.h>
using namespace std;
const int maxn=8e4+4e1+2,maxm=6e6+5e5+8e1+1,inf=0x7fffffff;
int m,n,s,t,a[41][101],tot=1,head[maxn],dis[maxn],flow[maxn],pre[maxn],last[maxn],maxflow,mincost,p[41],sum;
int cook[maxn],dish[maxn];
bool vis[maxn];
queue<int>q;
struct edge
{
int to,next,w,dis;
}e[maxm];
void addedge(int x,int y,int w,int dis)
{
e[++tot].to=y;e[tot].w=w;e[tot].dis=dis;e[tot].next=head[x];head[x]=tot;
e[++tot].to=x;e[tot].w=0;e[tot].dis=-dis;e[tot].next=head[y];head[y]=tot;
}
bool spfa()
{
memset(dis,0x7f,sizeof(dis));
memset(last,0,sizeof(last));
memset(vis,0,sizeof(vis));
flow[s]=inf;
q.push(s);vis[s]=1;dis[s]=0;pre[t]=-1;
while(!q.empty())
{
int now=q.front();q.pop();
vis[now]=0;
for(int i=head[now];i;i=e[i].next)
{
int y=e[i].to;
if(e[i].w>0&&dis[y]>dis[now]+e[i].dis)
{
dis[y]=dis[now]+e[i].dis;
pre[y]=now;
last[y]=i;
flow[y]=min(flow[now],e[i].w);
if(!vis[y])
{
q.push(y);
vis[y]=1;
}
}
}
}
if(pre[t]==-1)return 0;
int now=t;
maxflow+=flow[t];
mincost+=flow[t]*dis[t];
while(now!=s)
{
e[last[now]].w-=flow[t];
e[last[now]^1].w+=flow[t];
now=pre[now];
}
return 1;
}
int main()
{
ios::sync_with_stdio(0);
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&p[i]),sum+=p[i];
s=0,t=m*sum+n+1;
for(int i=1;i<=n;i++)addedge(s,i+sum*m,p[i],0);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
scanf("%d",&a[i][j]);
addedge(i+sum*m,sum*(j-1)+1,1,a[i][j]);
}
for(int i=1;i<=m;i++)addedge((i-1)*sum+1,t,1,0);
for(int i=1;i<=m;i++)
for(int j=1;j<=sum;j++)
{
int temp=(i-1)*sum+j;
dish[temp]=j,cook[temp]=i;
}
while(spfa())
{
int temp=e[last[t]^1].to;
addedge(temp+1,t,1,0);
for(int i=1;i<=n;i++)
addedge(i+m*sum,temp+1,1,a[i][cook[temp+1]]*dish[temp+1]);
}
printf("%d\n",mincost);
return 0;
}