洛谷P4014 分配问题【最小/大费用流】题解+AC代码
题目描述
有 n 件工作要分配给 n 个人做。第 i 个人做第 j 件工作产生的效益为c ij。 试设计一个将 n 件工作分配给 n 个人做的分配方案,使产生的总效益最大。
输入格式:
文件的第 1 行有 1 个正整数 n,表示有 n 件工作要分配给 n 个人做。接下来的 n 行中,每行有 n 个整数c ij,表示第 i 个人做第 j 件工作产生的效益为c ij 。
输出格式:
两行分别输出最小总效益和最大总效益。
输入样例
5
2 2 2 1 2
2 3 1 2 4
2 0 1 1 1
2 3 4 3 3
3 2 1 2 1
输出样例
5
14
说明
1001≤n≤100
题目分析
首先将讲讲最小收益
超级源点向每个工人连边
容量为1,费用为0
表示限制每个工人只能有一个工作
每个工人向每个工作连边
容量为1,费用为既定收益
这样当这条边有流时
费用即为收益
每个工作向超级汇点连边
容量为1,费用为0
表示限制每个工作只能被做一次
然后跑费用流就可以了
最大收益,即最大费用流
将原图中的边的费用全部取反
然后跑费用流
最后费用取反即可
这里由于要跑两次费用流
蒟蒻的我只想到了把原来存边的数组开了二维
即E[0][]表示最小费用用的图,E[1][]表示最大费用用的图
不知道有没有更好的办法
有没有哪位daolao教教我QAQ
#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
const int inf=2139062143;
int m,n;
int s=0,t;
struct node{int v,f,c,nxt;}E[2][1000010];
int head[2][1000010];//这里是蒟蒻开二维存图的方式
int d[2][1000010];
bool vis[1000010];
int ans,tot=1;
int read()
{
int f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return f*x;
}
void add(int u,int v,int f,int c,int p)
{
E[p][++tot].nxt=head[p][u];
E[p][tot].v=v;
E[p][tot].f=f;
E[p][tot].c=c;
head[p][u]=tot;
}
bool bfs(int p)
{
memset(d,127,sizeof(d)); d[p][s]=0;
queue<int> q; q.push(s); vis[s]=true;
while(!q.empty())
{
int u=q.front();
q.pop(); vis[u]=false;
for(int i=head[p][u];i;i=E[p][i].nxt)
{
int v=E[p][i].v;
if(E[p][i].f&&d[p][v]>d[p][u]+E[p][i].c)
{
d[p][v]=d[p][u]+E[p][i].c;
if(!vis[v])
{
q.push(v);
vis[v]=true;
}
}
}
}
return d[p][t]!=inf;
}
int dfs(int u,int cap,int p)
{
if(u==t)
return cap;
int flow=cap;
vis[u]=true;
for(int i=head[p][u];i;i=E[p][i].nxt)
{
int v=E[p][i].v;
if(d[p][v]==d[p][u]+E[p][i].c&&E[p][i].f&&flow&&!vis[v])
{
int f=dfs(v,min(E[p][i].f,flow),p);
flow-=f;
E[p][i].f-=f;
E[p][i^1].f+=f;
ans+=E[p][i].c*f;
}
}
vis[u]=false;
return cap-flow;
}
int main()
{
n=read();
t=n*2+1;
for(int i=1;i<=n;i++)
{
add(s,i,1,0,0);add(i,s,0,0,0);
add(s,i,1,0,1);add(i,s,0,0,1);
//超源向工人连边
add(i+n,t,1,0,0);add(t,i+n,0,0,0);
add(i+n,t,1,0,1);add(t,i+n,0,0,1);
//工作向超汇连边
}
for(int i=1;i<=n;i++)
{
for(int j=n+1;j<=n*2;j++)
{
int c=read();
add(i,j,1,c,0);add(j,i,0,-c,0);
add(i,j,1,-c,1);add(j,i,0,c,1);
//工人向工作连边
}
}
while(bfs(0))//最小费用流
dfs(s,inf,0);
cout<<ans<<endl;
ans=0;
while(bfs(1))//最大费用流
dfs(s,inf,1);
cout<<-ans<<endl;
return 0;
}