题解P2050【美食节】 费用流
litble
2017-12-10 11:50:49
# 题目分析
## 建图模型
这题就是P2053修车的数据加强版,那么建图方式也差不多。
对于每个菜建立一个点,源点向其连一条流量为需求量费用为0的边。
然后再建一层点,分别表示第j个厨师做第倒数i道菜。向汇点连一条流量为1费用为0的边。
假设有一个点表示第j个厨师做第倒数k道菜,那么对于菜i,向其连一条流量为1,费用为k×a(i,j)的边。这表示第j个厨师做的倒数第k道菜是菜i,那么就要做a(i,j)这么长的时间,有k个人要等这么长的时间。
意会一下可以发现,这个模型能解决“同时做”问题。
## 优化
由于此题数据量很大,把所有边连完后再跑费用流是一定会GG的(60分)
由于我们跑一次spfa只能找出一次增广路,所以我们可以暂时不连不需要的边。一开始,我们把所有厨师做倒数第1道菜与所有菜连好,然后找一条增广路,这条增广路上一定经过了一个点,表示第j个厨师做倒数第1道菜,于是我们添加点(第j个厨师做倒数第2道菜),与汇点和所有菜连边,以此类推。
意会一下可以发现,这样每次spfa的时候,需要的边都被连上了。
# 代码
```cpp
#include<bits/stdc++.h>
using namespace std;
const int N=100005,M=1e7+5,inf=0x3f3f3f3f;//边一定要开大
int m,n,tot=1,sum,S,T,ans;
int p[44],a[44][105],dish[N],cook[N];
int h[N],ne[M],to[M],flow[M],w[M],dis[N],liu[N],pre[N],inq[N];
void add(int x,int y,int z,int c) {
to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z,w[tot]=c;
to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0,w[tot]=-c;
}
int bfs() {//费用流:主干部分
for(int i=S;i<=T;++i) dis[i]=inf,inq[i]=pre[i]=0;
queue<int>q;int x;
liu[S]=inf,dis[S]=0,q.push(S);
while(!q.empty()) {
x=q.front(),q.pop(),inq[x]=0;
for(int i=h[x];i;i=ne[i])
if(flow[i]>0&&dis[x]+w[i]<dis[to[i]]) {
dis[to[i]]=dis[x]+w[i],pre[to[i]]=i;
liu[to[i]]=min(liu[x],flow[i]);
if(!inq[to[i]]) inq[to[i]]=1,q.push(to[i]);
}
}
if(!pre[T]) return 0;
x=T;
while(x!=S) {
int kl=pre[x];
flow[kl]-=liu[T],flow[kl^1]+=liu[T];
ans+=w[kl]*liu[T],x=to[kl^1];
}
return 1;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) scanf("%d",&p[i]),sum+=p[i];
for(int i=1;i<=n;++i) add(0,i+sum*m,p[i],0);
S=0,T=sum*m+n+1;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
scanf("%d",&a[i][j]),add(i+sum*m,(j-1)*sum+1,1,a[i][j]);//预先加上所有菜被厨师倒数第1个做
for(int i=1;i<=m;++i) add((i-1)*sum+1,T,1,0);//预先加上所有厨师做倒数第1道菜
for(int i=1;i<=m;++i)
for(int j=1;j<=sum;++j) {
int tmp=(i-1)*sum+j;
dish[tmp]=j,cook[tmp]=i;
//这是本蒟蒻为了方便解码而写的,表示tmp这个点代表cool[tmp]做倒数第dish[tmp]道菜
}
while(bfs()) {
int tmp=to[pre[T]^1];
add(tmp+1,T,1,0);//添加新的边,说明见上
for(int i=1;i<=n;++i)
add(i+m*sum,tmp+1,1,a[i][cook[tmp]]*(dish[tmp]+1));
}
printf("%d",ans);
return 0;
}
```