poj3686 The Windy's (转化为指派问题后KM或MCMF)

问题描述

文德玩具厂是世界著名的玩具厂,拥有 M个 世界一流的玩具生产车间。今年经理收到了N个玩具订单。经理知道每个订单在不同的车间需要不同的时间。更准确地说,如果玩具是在第j个车间生产的,第i个订单将需要Zij个小时。此外,每个订单的工作必须完全在同一个车间完成。一个车间要完成前一个订单才能转到下一个订单。这种转换不需要任何时间。
经理想要最小化N个订单的平均完成时间。你能帮助他吗?

输入

输入的第一行是测试用例的数量。每个测试用例的第一行包含两个整数,N和M(1≤N,M≤50)。
接下来的N行每个包含M个整数,描述矩阵Zij(1≤Zij≤100,000)每个测试用例前有一个空行。
输出
对于每个测试用例,将答案输出到一行中。结果应该四舍五入到小数点后六位。

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

思路:

如果每个工厂只能完成一个订单,那就是简单指派问题了。跑一遍KM或者MCMF就解决了。但是这个题目中每个工厂可能完成多个。

对一个工厂来说,如果每个订单都在这个工厂完成的话,那么:
第一个完成时间为t
第二个完成时间为t+t
第三个完成时间为t+t+t

把每个工厂拆成n个点,时间分别为t,t+t,t+t+t,…
然后就变成指派问题求最优解了,跑KM或MCMF

MCMF就是加个源点汇点,不写了

code(KM):
#include<cstring>
#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<map>
using namespace std;
const int maxm=55;
const int inf=1e9;
int z[maxm][maxm];
int g[maxm][maxm*maxm];
int lx[maxm],ly[maxm*maxm];
int visx[maxm],visy[maxm*maxm];
int need[maxm*maxm];
int now[maxm*maxm];
int nx,ny;
int n,m;
bool dfs(int x){
    visx[x]=1;
    for(int i=1;i<=ny;i++){
        if(!visy[i]){
            int temp=lx[x]+ly[i]-g[x][i];
            if(temp==0){
                visy[i]=1;
                if(now[i]==-1||dfs(now[i])){
                    now[i]=x;
                    return 1;
                }
            }else{
                need[i]=min(need[i],temp);
            }
        }
    }
    return 0;
}
void km(){
    for(int i=1;i<=ny;i++){
        now[i]=-1;
        ly[i]=0;
    }
    for(int i=1;i<=nx;i++){
        lx[i]=g[i][1];
        for(int j=2;j<=ny;j++){
            lx[i]=max(lx[i],g[i][j]);
        }
    }
    for(int i=1;i<=nx;i++){
        for(int j=1;j<=ny;j++){
            need[j]=inf;
        }
        while(1){
            for(int j=1;j<=nx;j++){
                visx[j]=0;
            }
            for(int j=1;j<=ny;j++){
                visy[j]=0;
            }
            if(dfs(i))break;
            int d=inf;
            for(int j=1;j<=ny;j++){
                if(!visy[j]){
                    d=min(d,need[j]);
                }
            }
            for(int j=1;j<=nx;j++){
                if(visx[j]){
                    lx[j]-=d;
                }
            }
            for(int j=1;j<=ny;j++){
                if(visy[j]){
                    ly[j]+=d;
                }else{
                    need[j]-=d;
                }
            }
        }
    }
    int ans=0;
    for(int i=1;i<=ny;i++){
        if(now[i]!=-1){
            ans+=g[now[i]][i];
        }
    }
    ans=-ans;
    printf("%.6f\n",ans*1.0/n);
}
int main(){
    int T=11;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                scanf("%d",&z[i][j]);//i to j
            }
        }
        nx=n;
        ny=n*m;
        for(int i=1;i<=n;i++){//toy
            int cnt=0;
            for(int j=1;j<=m;j++){//factor
                for(int k=1;k<=n;k++){//拆成n个点
                    cnt++;
                    g[i][cnt]=-k*z[i][j];
                }
            }
        }

        km();
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值