题意:
N个工件要在M个工厂加工,一个工件必须在一个工厂做完,工厂一次只能处理一个工件。给定每个工件在每个工厂加工所需时间,求出每个工件加工结束的最小时间平均值。
分析:
工厂一次只能处理一个工件,那么其他要在这个工厂处理的工件就要排队等待,如果有
a
个工件要在该厂处理,花的时间分别为
代码:
最小费用最大流解法:
#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的幂~