POJ 3686_The Windy's

题意:

N个工件要在M个工厂加工,一个工件必须在一个工厂做完,工厂一次只能处理一个工件。给定每个工件在每个工厂加工所需时间,求出每个工件加工结束的最小时间平均值。

分析:

工厂一次只能处理一个工件,那么其他要在这个工厂处理的工件就要排队等待,如果有a个工件要在该厂处理,花的时间分别为n1,n1+n2,...,n1+n2+n3..na,该工厂花的总时间就为an1+(a1)n2+...+1na,这样将每个工厂拆为N个点,表示每个工件的完成花费了1..N倍的时间(别的工件等待的时间+处理该工件的时间),便可以转化为二分图最大权匹配问题。

代码:

最小费用最大流解法:

#include<cstdio>
#include<vector>
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
#define se second
#define fi first
typedef pair<int, int>pii;//first 顶点距离,secon顶点编号
struct edge{int to, cap, cost, rev;};
const int maxn = 30005, maxm = 500, INF =0x3f3f3f3f;
int V, s, t;
vector<edge>G[maxn];
int dist[maxn], prevv[maxn], preve[maxn], h[maxn];//h记录顶点的势
int z[maxm][maxm];
void add_edge(int from, int to, int cap, int cost)
{
    G[from].push_back((edge){to, cap, cost, G[to].size()});
    G[to].push_back((edge){from, 0, -cost, G[from].size() - 1});
}
int min_cost_flow(int s, int f)
{
    int res = 0;
    fill(h, h + V + 1, 0);
    while(f > 0){
        priority_queue<pii, vector<pii>, greater<pii> >que;
        fill(dist, dist + V + 1, INF);
        dist[s] = 0;
        que.push(pii(0, s));
        while(!que.empty()){
            pii p = que.top();que.pop();
            int v = p.se;
            if(dist[v] < p.fi) continue;
            for(int i = 0; i < G[v].size(); i++){
                edge &e = G[v][i];
                if(e.cap>0&&dist[e.to]>dist[v] + e.cost + h[v] - h[e.to]){
                    dist[e.to] = dist[v] + e.cost + h[v] - h[e.to];
                    prevv[e.to] = v; preve[e.to] = i;
                    que.push(pii(dist[e.to], e.to));
                }
            }
        }
        if(dist[t] == INF) return -1;
        for(int i = 1; i <= V; i++) h[i] +=dist[i];
        int d = f;
        for(int v = t; v != s; v = prevv[v]){
            d = min(d, G[prevv[v]][preve[v]].cap);
        }
        f -= d;
        res += d * h[t];
        for(int v = t; v!= s; v = prevv[v]){
            edge &e = G[prevv[v]][preve[v]];
            e.cap -= d;
            G[v][e.rev].cap += d;
        }
    }
    return res;
}
int main (void)
{
    int c;scanf("%d",&c);
    while(c--){
          memset(G, 0, sizeof(G));
          int N, M;scanf("%d%d",&N,&M);
            s = N * (M + 1), t = s + 1;
            for(int i = 0; i < N; i++){
                add_edge(s, i, 1, 0);
                for(int j = 0; j < M; j++){
                    scanf("%d",&z[i][j]);
                }
            }
            for(int j = 0; j < M; j++){
                for(int k = 0; k < N; k++){
                    add_edge(N + j * N + k, t, 1, 0);
                    for(int i = 0; i < N; i++)
                        add_edge(i, N + j * N + k, 1, z[i][j] * (k + 1));
                }
        }
        V = t + 1;
        printf("%.6f\n", (double)min_cost_flow(s, N)/N);
    }

}

KM算法:

很清晰的KM讲解1
很清晰的讲解2
非常生动的匈牙利算法讲解
求解二分图最大权匹配,KM时间复杂度O(n3),比最小费用最大流解法快多了!

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
int N, M, nm, nn;
const int maxn = 2555, INF = 0x3f3f3f3f;
int usex[55], usey[maxn], match[maxn], lx[55], ly[maxn], slack[maxn];
int z[55][maxn];
bool Find(int x)
{
    usex[x] = 1;
    for(int i = 0; i < nm; i++){
        if(usey[i]) continue;
        int s = lx[x] + ly[i] - z[x][i];
        if(s == 0){
            usey[i] = 1;
            if(match[i] == -1|| Find(match[i] )){
                match[i] = x;
                return true;
            }
        }else if(slack[i] > s){
            slack[i] = s;
        }
    }
    return false;
}
int KM()
{
    memset(ly, 0, sizeof(ly));
    memset(match, -1, sizeof(match));
    for(int i = 0; i <nn; i++){
        lx[i] = - INF;
        for(int j = 0; j < nm; j++){
            if(lx[i] < z[i][j])
                lx[i] = z[i][j];
        }
    }
    for(int a = 0; a < nn; a++){
        memset(slack, 0x3f, sizeof(slack));
        for(;;){
            memset(usex, 0,sizeof(usex));
            memset(usey, 0, sizeof(usey));
            if(Find(a)) break;
            int d = INF;
            for(int i = 0; i < nm; i++){
                if(!usey[i] && d > slack[i]){
                    d = slack[i];
                }
            }
            for(int i = 0; i < nn; i++){
                if(usex[i]) lx[i] -= d;
            }
            for(int i = 0; i < nm; i++){
                if(usey[i]) ly[i] += d;
                else slack[i] -= d;
            }
        }
    }
    int sum = 0;
    for(int i = 0; i < nm; i++){
        if(match[i] > -1){
            sum += z[match[i]][i];
        }
    }
    return -sum;
}
int main (void)
{
    int c;scanf("%d",&c);
    while(c--){
        scanf("%d%d",&N,&M);
        int t;
        nn = N, nm = N * M;
        for(int i = 0; i < N; i++){
            for(int j = 0; j < M; j++){
                scanf("%d",&t);
                for(int k = 0; k < N; k++)
                    z[i][j * N + k] = - t * (k + 1);
            }
        }
        printf("%.6f\n", (double)KM()/N);
    }
}

垃圾WA,垃圾垃圾!


关于KM的拓展:

  • 如果是求最小权,则可以把lx[i[初始化为min(g[i][j]),并在find函数中进行相应改变,也可以简单的将所有边取负数,计算最大权,然后结果再取负数即可。
  • 权的最大积的话,把边取对数,然后计算最大权就好啦~最后记得答案要取e的幂~
posted @ 2016-03-04 21:20  zhuyujiang  阅读(108)  评论(0编辑  收藏  举报