题意:有
n
n
n辆车待修,有
m
m
m个修车师傅,他们修相同的车所需的时间可能不同,问修车师傅修完所有车的总时间。
可以把修车师傅分成
n
n
n个点,这样会得到一个
m
×
n
m\times n
m×n的点阵,
(
i
,
j
)
(i,j)
(i,j)这个点表示师傅
i
i
i倒数第
j
j
j辆修的车是啥。
倒过来设置是因为计算贡献方便,倒数第
j
j
j辆修的车会让
j
j
j后面的车都等上更长的时间。设修理这辆车的时间为
t
t
t,这条边的权值就是
j
×
t
j\times t
j×t。
c
o
d
e
:
code:
code:
#include <bits/stdc++.h>
#define regi register int
int n,m;
int S,T;
int ans;
int mincost;
int vis[100100];
int dis[100100];
std::queue<int>q;
int a[1000][1000];
int head[100100],tot=1;
struct edge{
int to;
int nxt;
int flow;
int cost;
}e[100000];
void add(int x,int y,int flow,int cost){
e[++tot]={y,head[x],flow,cost};
head[x]=tot;
e[++tot]={x,head[y],0,-cost};
head[y]=tot;
}
bool spfa(){
for(regi i=1;i<=T*3;++i){
dis[i]=0x3f3f3f3f;
vis[i]=0;
}
q.push(S);
dis[S]=0;
while(!q.empty()){
int x=q.front();
q.pop();
vis[x]=0;
for(regi i=head[x];i;i=e[i].nxt){
regi y=e[i].to;
if(e[i].flow&&dis[y]>dis[x]+e[i].cost){
dis[y]=dis[x]+e[i].cost;
if(!vis[y]){
vis[y]=1;
q.push(y);
}
}
}
}
return dis[T]!=0x3f3f3f3f;
}
int dfs(int x,int min){
if(x==T){
vis[T]=1;
return min;
}
vis[x]=1;
int flow=0;
for(regi i=head[x],w;i;i=e[i].nxt){
int y=e[i].to;
if((!vis[y]||y==T)&&e[i].flow&&dis[y]==dis[x]+e[i].cost)
if(w=dfs(y,std::min(min-flow,e[i].flow))>0){
mincost+=w*e[i].cost;
e[i].flow-=w;
e[i^1].flow+=w;
flow+=w;
}
}
return flow;
}
void dinic(){
while(spfa()){
vis[T]=1;
while(vis[T]){
memset(vis,0,sizeof vis);
dfs(S,0x3f3f3f3f);
}
}
}
main(){
scanf("%d%d",&m,&n);
S=n*m+n+1;
T=S+1;
for(regi i=1;i<=n;++i)
for(regi j=1;j<=m;++j)
scanf("%d",&a[i][j]);
//a[i][j]表示第i辆车第j个人修的时间。
for(regi i=1;i<=n;++i)
add(S,n*m+i,1,0);
for(regi i=1;i<=m;++i)
for(regi j=1;j<=n;++j){
for(regi k=1;k<=n;++k)
add(n*m+k,(i-1)*n+j,1,a[k][i]*j);
add((i-1)*n+j,T,1,0);
}
//连边。
dinic();
printf("%.2lf\n",mincost*1.0/n);
//输出总时间/n即可。
return 0;
}
那么考虑一下美食节这题的区别,无非就是无法一下子建出所有的边,一个厨师如果还没有烧倒数第一道菜,就不可能去烧倒数第二道菜,所以现在一下子建出所有的边是不优的,可以在一个厨师做完菜之后再扩点继续跑费用流。
c
o
d
e
:
code:
code:
//首先把所有厨师倒数第一次做什么菜安排了
//之后跑一边
//然后把使用的厨师扩点
#include <bits/stdc++.h>
#define regi register int
int n,m;
int ans;
int cnt,tot=1;
int S=1,T=2;
int ok[50];
int need[50];
int use[110];
int dis[1010000];
int vis[1010000];
int head[1010000];
int flow[1010000];
int last[1010000];
//last表示流向这个点的边是哪条
int cook[50][110];
int belone[1000];
struct edge{
int to;
int nxt;
int flow;
int cost;
}e[1010000];
std::queue<int>q;
void add(int x,int y,int flow,int cost){
e[++tot]={y,head[x],flow,cost};
head[x]=tot;
e[++tot]={x,head[y],0,-cost};
head[y]=tot;
}
bool spfa(){
for(int i=1;i<=cnt;++i){
dis[i]=0x3f3f3f3f;
vis[i]=0;
flow[i]=0x3f3f3f3f;
last[i]=0;
}
q.push(S);
vis[S]=1;
dis[S]=0;
while(!q.empty()){
int X=q.front();
q.pop();
vis[X]=0;
for(regi i=head[X];i;i=e[i].nxt){
regi y=e[i].to;
if(e[i].flow&&dis[y]>dis[X]+e[i].cost){
dis[y]=dis[X]+e[i].cost;
last[y]=i;
flow[y]=std::min(flow[X],e[i].flow);
if(!vis[y]){
vis[y]=1;
q.push(y);
}
}
}
}
return dis[T]!=0x3f3f3f3f;
}
void dinic(){
while(spfa()){
for(regi x=T;last[x];x=e[last[x]^1].to){
e[last[x]].flow-=flow[T];
e[last[x]^1].flow+=flow[T];
ans+=flow[T]*e[last[x]].cost;
}
int X=belone[e[last[T]^1].to];
//找到那个使用的厨师
belone[++cnt]=X;
add(cnt,T,1,0);
for(regi i=1;i<=n;++i)
add(i+2,cnt,1,(use[X]+1)*cook[i][X]);
++use[X];
}
}
main(){
scanf("%d%d",&n,&m);
for(regi i=1;i<=n;++i){
scanf("%d",&need[i]);
add(S,i+2,need[i],0);
}
for(regi i=1;i<=n;++i)
for(regi j=1;j<=m;++j)
scanf("%d",&cook[i][j]);
cnt=n+2;
for(regi i=1;i<=m;++i){
belone[++cnt]=i;
add(cnt,T,1,0);
for(regi j=1;j<=n;++j)
add(j+2,cnt,1,(use[i]+1)*cook[j][i]);
++use[i];
}
dinic();
printf("%d\n",ans);
return 0;
}
/*代码实现:
开一个数组记录每个厨师现在是在做他的倒数第几碗菜。这个是为了方便之后的连边
记录一下这个厨师分出来的这个点对应源点连向的哪个点,这样可以确定这次是哪个厨师做了菜
其实这张图就四层,去了源点和汇点就两层,所以寻找那个流过的点十分简单,让终点往前一步就能找到,然后造出这个点的下一个点。
*/