您的位置:首页 > 其它

【POJ 3686】【最小费用最大流或者KM算法 指派问题变形(需要拆点)】The Windy's n个玩具指派给m个工厂生产

2016-09-16 15:29 423 查看
传送门:POJ 3686 The Windy's

描述:

The Windy's

Time Limit: 5000MS Memory Limit: 65536K
Total Submissions: 5007 Accepted: 2105
Description

The Windy's is a world famous toy factory that owns M top-class workshop to make toys. This year the manager receives N orders for toys. The manager knows that every order will take different amount of hours in different workshops. More
precisely, the i-th order will take Zij hours if the toys are making in the j-th workshop. Moreover, each order's work must be wholly completed in the same workshop. And a workshop can not switch to another order until
it has finished the previous one. The switch does not cost any time.

The manager wants to minimize the average of the finishing time of the N orders. Can you help him?

Input

The first line of input is the number of test case. The first line of each test case contains two integers, N and M (1 ≤ N,M ≤ 50).

The next N lines each contain M integers, describing the matrix Zij (1 ≤ Zij ≤ 100,000) There is a blank line before each test case.

Output

For each test case output the answer on a single line. The result should be rounded to six decimal places.

Sample Input
3

3 4
100 100 100 1
99 99 99 1
98 98 98 1

3 4
1 100 100 100
99 1 99 99
98 98 1 98

3 4
1 100 100 100
1 99 99 99
98 1 98 98

Sample Output
2.000000
1.000000
1.333333

Source

POJ Founder Monthly Contest – 2008.08.31, windy7926778

题意:
有N个玩具,M个玩具厂。给出每个玩具厂加工每个玩具的时间。每个工厂只能同时加工一个玩具。求玩具加工分配方案,使每个玩具的平均加工时间最小。

思路一:

因为玩具的个数是个定值,所以我们只需最小化加工所有玩具的总时间就可以。

假设某个机器处理了k个玩具,那么对于这些玩具,有两种时间,一种是真正处理的时间,一种是等待的时间,等待的时间就是之前所有处理的玩具的时间,

假设这k个玩具真正用在加工的时间分为a1,a2,a3...ak, 那么每个玩具实际的时间是加工的时间+等待时间,分别为

a1, a1+a2, a1+a2+a3.......a1+a2+...ak。

求和之后变为 a1 *k + a2 * (k - 1) + a3 * (k - 2).... + ak

这时就发现,每个玩具之间的实际时间可以分开来算 然后求和了。

可以发现,我们只需贪心的从花费时间最小的玩具开始加工,就可以得到最小的总的加工时间。

但是对于多个厂来加工玩具的问题,这样贪心就不可以了。

我们重新看一下求出的式子,除了可以看做一个厂加工多个玩具之外,还可以看做多个只能加工一个玩具的厂来加工这些玩具,只不过花费的时间将改变为1-N倍。

这样,我们就把原问题变成了一般的指派问题。

因为对每个机器,最多可以处理n个玩具,所以可以拆成n个点,1~n分别代表某个玩具在这个机器上倒数第几个被加工的, 所以我们对于每个玩具i,机器j中拆的每个点k,连接一条z[i][j]*k权值的边

建图过程:

1.建立源汇点,源点和每个订单流量为1,费用为0;

2.玩具和每台机器拆成的点k建立流量为1,费用为Mij*k;

3.k与汇点建立流量为1,费用为0

代码一:

//G++ 1125ms 5796k
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#include <vector>
#define ll __int64
using  namespace std;

//最小费用大流,求只需要取相反数结果即可。
//点的总数为 N,点的编号 0~N
const int maxn=10000;
const int maxm=500000;
const int inf=0x3f3f3f3f;

struct Edge{
int to,next,cap,flow,cost;
}es[maxm];
int head[maxn],tol;
int p[maxn];//记录增广路径上 到达点i的边的编号
int d[maxn];//单位总费用
bool vis[maxn];
int N;//节点总个数
int  sink,source;
int n,m;
int mp[55][55];

void init(int n){
N=n;
tol=0;
memset(head, -1, sizeof(head));
}

void addedge(int u,int v,int cap,int cost){
es[tol].to=v;
es[tol].cap=cap;
es[tol].cost=cost;
es[tol].flow=0;
es[tol].next=head[u];
head[u]=tol++;
es[tol].to=u;
es[tol].cap=0;
es[tol].cost=-cost;
es[tol].flow=0;
es[tol].next=head[v];
head[v]=tol++;
}

bool spfa(int s,int t){//寻找花销最少的路径
//跑一遍SPFA 找s——t的最少花销路径 且该路径上每一条边不能满流
//若存在 说明可以继续增广,反之不能
queue<int>q;
for(int i=0; i<=N; i++){
d[i]=inf;
vis[i]=false;
p[i]=-1;
}
d[s]=0;
vis[s]=true;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=false;
for(int i=head[u]; i!=-1; i=es[i].next){ //逆向枚举以u为起点的边
int v=es[i].to;
if(es[i].cap>es[i].flow && d[v]>d[u]+es[i].cost){//可以松弛 且 没有满流
d[v]=d[u]+es[i].cost;
p[v]=i;   //记录前驱边 的编号
if(!vis[v]){ vis[v]=true; q.push(v);}
}
}
}
return p[t]!=-1;//可达返回true
}

//返回的是最大流,cost存的是最小费用
int MCMF(int s,int t,int &cost){
int flow = 0;cost = 0;
while(spfa(s,t)){//每次寻找花销最小的路径
int Min=inf;
//通过反向弧 在源点到汇点的最少花费路径 找最小增广流
for(int i=p[t]; i!=-1; i=p[es[i^1].to]){
if(Min>es[i].cap-es[i].flow)
Min=es[i].cap-es[i].flow;
}
//增广
for(int i=p[t]; i!=-1; i=p[es[i^1].to]){
es[i].flow+=Min;
es[i^1].flow-=Min;
cost+=es[i].cost*Min;//增广流的花销
}
flow+=Min;//总流量累加
}
return flow;
}

void getmap(){
for(int i=1; i<=n; i++)
addedge(source, i, 1, 0);
for(int i=1; i<=n; i++)//n个玩具
for(int j=1; j<=m; j++)//n个工厂
for(int k=1; k<=n; k++)//拆成k个点
addedge(i, n+(j-1)*n+k, 1, mp[i][j]*k);//这段代码需要仔细想一下
//本质上是把制作多个玩具的工厂拆成多个只制造一个玩具的工厂,不过各自需要花1倍到N倍的时间
for(int i=n+1; i<=n+m*n; i++)
addedge(i, sink, 1, 0);
}

int  main(){
//  #ifndef ONLINE_JUDGE
//  freopen("in.txt","r",stdin);
//  #endif

int t;
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&m);
sink=0;source=n+n*m+1;
init(n+n*m+1);
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
scanf("%d",&mp[i][j]);
getmap();
int cost;
MCMF(source, sink, cost);
printf("%.6lf\n",cost*1.0/n);//POJ上要将lf改成f
}
return 0;
}


思路二:
另一种做法是用KM算法来实现

m个机器,对每个机器,最多可以处理n个玩具,拆成n个点,1~n分别代表某个玩具在这个机器上倒数第几个被加工的,对于每个玩具i,机器j中拆的每个点k,连接一条w[i][j]*k权值的边,就得到了n个点和n*m个点的最小权匹配问题,这里求得是最小值,可将权值取相反数,结果取相反数。

这个做法比费用流的做法效率高,而且还好想~

代码二:
//G++ 16ms 1212k
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#include <vector>
#define ll __int64
using namespace std;

/* KM算法
* 复杂度O(nx*nx*ny)
* 求最大权匹配
* 若求最小权匹配,可将权值取相反数,结果取相反数
* 点的编号从1开始
*/
const int N=55;
const int inf=0x3f3f3f3f;
int nx,ny;//两边的点数
int g
[2555];//二分图描述
int link[2555],lx
,ly[2555];//y中各点匹配状态,x,y中的顶点标号
int slack[2555];
bool visx
,visy[2555];
int n,m;

bool dfs(int x){
visx[x]=true;
for(int y=1; y<=ny; y++){
if(visy[y])continue;
int tmp=lx[x]+ly[y]-g[x][y];
if(tmp==0){
visy[y]=true;
if(link[y]==-1 || dfs(link[y])){
link[y]=x;
return true;
}
}
else if(slack[y]>tmp) slack[y]=tmp;
}
return false;
}

int KM(){
memset(link, -1, sizeof(link));
memset(ly, 0, sizeof(ly));
for(int i=1; i<=nx; i++){
lx[i]=-inf;
for(int j=1; j<=ny; j++)
if(g[i][j]>lx[i])
lx[i]=g[i][j];
}
for(int x=1; x<=nx; x++){
for(int i=1; i<=ny; i++) slack[i]=inf;
while(true){
memset(visx, false, sizeof(visx));
memset(visy, false, sizeof(visy));
if(dfs(x))break;
int d=inf;
for(int i=1; i<=ny; i++)
if(!visy[i] && d>slack[i])
d=slack[i];
for(int i=1; i<=nx; i++)
if(visx[i])
lx[i]-=d;
for(int i=1; i<=ny; i++){
if(visy[i])ly[i]+=d;
else slack[i]-=d;
}
}
}
int res=0;
for(int i=1; i<=ny; i++)
if(link[i]!=-1)
res+=g[link[i]][i];
return -res;
}

void getmap(){
scanf("%d%d",&n,&m);
nx=n;ny=n*m;
int w;
for(int i=1; i<=n; i++){
int cnt=1;
for(int j=1; j<=m; j++){
scanf("%d",&w);
for(int k=1; k<=n; k++){
g[i][cnt++]=-w*k;
}
}
}
}

int main(){
int t;
scanf("%d",&t);
while(t--){
getmap();
double ans=1.0*KM()/n;
printf("%.6f\n",ans);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  poj 费用流 二分图