POJ - 3686 The Windy's(KM二分图最小权匹配&拆点)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/kuronekonano/article/details/81751030

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

题意:有m个玩具厂和n个订单,每个玩具厂,每个玩具厂做不同的订单有不同的用时ai。求做完n个订单的最小平均时间。

如果每个厂都只能做一个任务,那么就是一个简单的二分图最小权匹配。但是每个厂昨晚之后可以继续做别的订单,一些订单多等待一会儿再做的总时间都比让给其他厂做的时间少。

可以知道,对于一个玩具厂做订单的时间总和为:
a1+(a1+a2)+(a1+a2+a3)+(a1+a2+a3+a4)+……+(a1+a2+a3+…ak)

提出相同时间后为:
a1*k+a2*(k-1)+a3*(k-2)+a4*(k-3)+……+ak*1

如果一个玩具在某个玩具厂中第一个开始做,那么后续做的玩具所用时间都会加上第一个玩具的制作时间,也就是后续玩具的等待时间都会增加这个值。

那么反过来说,即如果一个玩具是第k个开始制作,那么第k+1,k+2,k+3….n这些玩具的时间都会多出多出第i个玩具在第j个厂的制作时间。多出了多少,多出来k个。
举例来说,如果第一个制作这个玩具,那么他就是倒数第n个制作的,他给后续玩具增加了n次a[i][j]的时间,是倒数第k个制作的,就被计算了k次制作时间。

鉴于这一的考虑,我们将一个玩具厂拆点成1~n个玩具厂,对于这1~n个中的某一个,其意义是如果一个玩具在这个厂中第K个做,给制作所有玩具制作总时间所贡献的时间。这一拆点建图后就可以通过跑一个最小权匹配得到答案。

【求二分图的最小匹配】
只需把权值取反,变为负的,再用KM算出最大权匹配,取反则为其最小权匹配。

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int N = 110;
const int INF = 0x3f3f3f3f;
int nx,ny;///两边的点数
int g[N][N*50];///二分图描述
int linker[N*50],lx[N],ly[N*50];///y中各点匹配状态,x,y中的点标号
int slack[N*50];
bool visx[N],visy[N*50];
bool DFS(int x)
{
    visx[x] = true;///标记询问过
    for(int y = 1; y <= ny; y++)///查询一个x能匹配的所有y
    {
        if(visy[y])continue;///已经标记过的不能再查询
        int tmp = lx[x] + ly[y] - g[x][y];
        if(tmp == 0)///相等才能匹配
        {
            visy[y] = true;
            if(linker[y] == -1 || DFS(linker[y]))///判断y点是否被匹配过,或是否y点的匹配能找到增广路
            {
                linker[y] = x;///y点匹配x点
                return true;///增广路查找成功
            }
        }
        else if(slack[y] > tmp)///増广失败,更新记录最小到达y点的d值
            slack[y] = tmp;
    }
    return false;
}
int KM()
{
    memset(linker,-1,sizeof(linker));
    memset(ly,0,sizeof(ly));///初始化右节点权值,开始都是0
    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]取最大边权
                lx[i] = g[i][j];
    }
    for(int x = 1; x <= nx; x++)///匹配n个节点
    {
        for(int i = 0; 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++)///在剩余未匹配的y节点
                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(linker[i] != -1)
            res += g[linker[i]][i];
    return res;
}
int main()
{
    int n,m,t;
    scanf("%d",&t);
    while(t--)
    {
        int tmp;
        scanf("%d%d",&n,&m);
        for(int i=1; i<=n; i++)
            for(int j=1; j<=m; j++)
            {
                scanf("%d",&tmp);
                for(int k=1; k<=n; k++)
                    g[i][(j-1)*n+k]=-tmp*k;
            }
        nx=n,ny=n*m;
        double sum=-KM();
        printf("%f\n",sum/n);
    }
}

猜你喜欢

转载自blog.csdn.net/kuronekonano/article/details/81751030