运筹指派问题java代码_[管理运筹学]指派问题的匈牙利算法及其c++实现 (例:「网络流 24 题」分配问题 )...

题目描述

有 n 件工作要分配给 n 个人做。第 i 个人做第 j 件工作产生的效益为 c[i][j]​ 。试设计一个将 n 件工作分配给 n 个人做的分配方案,使产生的总效益最大。

输入格式:

文件的第 1 行有 1 个正整数 n,表示有 n 件工作要分配给 n个人做。

接下来的n 行中,每行有 n 个整数 c[i][j]​​,表示第 i个人做第 j 件工作产生的效益为 c[i][j]​。

输出格式:

两行分别输出最小总效益和最大总效益。

求最小总效益就是经典的指派问题,最大总收益只要把c乘-1再求最小就可以。

这里举个例子

n = 5,c为下表

12

7

9

7

9

8

9

6

6

6

7

17

12

14

9

15

14

6

6

10

4

10

7

10

9

第一步:每行减去该行最小的数,保证每行都有0。

5

0

2

0

2

2

3

0

0

0

0

10

5

7

2

9

8

0

0

4

0

6

3

6

5

第二步:每列减去该列最小的数,保证每行每列都有0。

5

0

2

0

2

2

3

0

0

0

0

10

5

7

2

9

8

0

0

4

0

6

3

6

5

第三步:

从单个0元素的行开始,给0加圈,记作O,然后划去所在行的其它0元素,记为X。

第四步:

从单个0元素的列开始,给0加圈,记作O,然后划去所在列的其它0元素,记为X。

重复三四,直到无法标记;

第五步:

若还存在没有画圈的0元素,则从剩余的0元素最少的行(列)开始,选0元素画圈,然后划掉同行同列的其它0元素,反复进行,直到所有0元素均被圈出或划掉为止;

检验:

若O的数目cnt=n,则该指派问题的最优解已经得到。

否则,进行调整。

5

O

2

X

2

2

3

X

X

O

O

10

5

7

2

9

8

O

X

4

X

6

3

6

5

例子cnt = 4,少一个O

调整:找最少覆盖所有0的直线

对没有O的行打√

对已打√行中含X所在列打√

对已打√列中含O所在行打√

重复2~3, 直至没有要打√的行和列为止

对没有打√的行划横线, 对打√的列划竖线,得到最少覆盖所有0的直线。

取未划线的最小数,未划线的减去这个数,线交点处加上这个数。

返回第一步。

打√后

5

O

2

X

2

2

3

X

X

O

O

10

5

7

2

√3

9

8

O

X

4

X

6

3

6

5

√1

√2

调整6后

7

0

2

0

2

4

3

0

0

0

0

8

3

5

0

11

8

0

0

4

0

4

1

4

3

正确性初步说明:同一行或同一列减去同一个数不影响最优分配方案

程序的一点说明:实际计算中, 第五步其实不需要dfs所有情况,模拟即可。相应调整5后可以这样: 设直线数为l,若l小于n,则转调整6;若l=n,则转第五步重新试探。程序直接枚举到最好的情况了,所以省了这个判断。

#include

#include

#include

using namespace std;

#define N 110

void Out(int a[N][N],int v[N][N],int n) {

for (int i = 1; i <= n; i++){

for (int j = 1; j <= n; j++) {

if(!v[i][j]) printf("%4d",a[i][j]);

else printf(" %c",v[i][j]==1?'O':'X');

}

puts("");

}puts("End!");

}

int HZ[N],LZ[N];

struct Pair {

int x,y;

Pair(int x = 0,int y = 0):x(x),y(y){}

bool operator < (const Pair & b) const {

return HZ[this->x]==HZ[b.x] ? LZ[this->y]x]

}

}Pt[N*N];

int FH[N],FL[N];

int Maxx,Mx;

int st[N*N],tot,used[N*N];

void dfs(int s,int t,int sum) {

if (sum > Mx) {

Mx = sum;

used[0] = tot;//记录最好方案

for (int i = 1; i <= tot; i++) used[i] = st[i];

}

if (Mx == Maxx) return;//已经找到满意解

if (t-s+1+sum <= Mx) return;//乐观估计不如目前最优解

if (s > t) return;

if (!FH[Pt[s].x] && !FL[Pt[s].y]) { //选s点

FH[Pt[s].x] = 1;

FL[Pt[s].y] = 1;

st[++tot] = s;

dfs(s+1,t,sum+1);

--tot;

FH[Pt[s].x] = 0;

FL[Pt[s].y] = 0;

}

dfs(s+1,t,sum);//不选s点

}

int calc(int b[N][N],int n) {

int a[N][N],v[N][N];

for (int i = 1; i <= n; i++)

for (int j = 1; j <= n; j++)

a[i][j] = b[i][j];

for (int i = 1; i <= n; i++) {

int t = a[i][1];

for (int j = 2; j <= n; j++)

if (a[i][j] < t) t = a[i][j];

for (int j = 1; j <= n; j++)

a[i][j] -= t;

}

for (int i = 1; i <= n; i++) {

int t = a[1][i];

for (int j = 2; j <= n; j++)

if (a[j][i] < t) t = a[j][i];

for (int j = 1; j <= n; j++)

a[j][i] -= t;

}

//先让每行每列都有0

//Out(a,v,n);

int H[N],L[N];

while(1) {

for (int i = 1; i <= n; i++) {

H[i] = L[i] = 0; //H[i],第i行有多少个0,L为列

FH[i] = FL[i] = 0; //FH[i],第i行有没有画O

for (int j = 1; j <= n; j++) v[i][j] = 0;//v[i][j] = 1代表‘O’,-1代表‘X’

}

for (int i = 1; i <= n; i++)

for (int j = 1; j <= n; j++)

if (a[i][j] == 0) {

H[i]++; L[j]++;

}

int cnt = 0;

while (1) {

int tpcnt = cnt;

for (int i = 1; i <= n; i++) //找每行单独的0画‘O’,同列画‘X’

if (H[i] == 1) {

int t = 1;

while(a[i][t] || v[i][t]) t++;

v[i][t] = 1;

cnt++; //cnt记有几个‘O’

H[i]--; L[t]--;

FH[i] = 1; FL[t] = 1;

for (int j = 1; j <= n; j++)

if (a[j][t]==0 && j!=i && v[j][t]==0) {

v[j][t] = -1;

H[j]--; L[t]--;

}

}

for (int i = 1; i <= n; i++) //对称的

if (L[i] == 1) {

int t = 1;

while(a[t][i] || v[t][i]) t++;

v[t][i] = 1;

cnt++;

H[t]--; L[i]--;

FH[t] = 1; FL[i] = 1;

for (int j = 1; j <= n; j++)

if (a[t][j]==0 && j!=i && v[t][j]==0) {

v[t][j] = -1;

H[t]--; L[j]--;

}

}

if (tpcnt == cnt) break;

}

//Out(a,v,n);

int top = 0;

for (int i = 1; i <= n; i++)

for (int j = 1; j <= n; j++)

if (a[i][j]==0 && v[i][j]==0) {

Pt[++top] = Pair(i,j);

HZ[i]++;

LZ[j]++;

}

sort(Pt+1,Pt+top+1);//同行同列少的排前面

Maxx = n-cnt;

Mx = 0; used[0] = 0;

dfs(1,top,0);//对剩下的0进行试探画‘O’

cnt += Mx;

for (int i = 1; i <= used[0]; i++) {

v[Pt[used[i]].x][Pt[used[i]].y] = 1;

FH[Pt[used[i]].x] = 1;

FL[Pt[used[i]].y] = 1;

}

//Out(a,v,n);

if (cnt == n) { //已经找到

int ans = 0;

for (int i = 1; i <= n; i++)

for (int j = 1; j <= n; j++)

if (v[i][j] == 1) ans += b[i][j];

return ans;

}

int flagx[N],flagy[N]; //对号标记

for (int i = 1; i <= n; i++) flagx[i] = flagy[i] = 0;

int cas = 1;//时间戳,每次只检查新增对号行/列

for (int i = 1; i <= n; i++)

if (!FH[i]) flagx[i] = cas;

bool chang = 1;

while (chang) {

chang = 0;

cas++;

for (int i = 1; i <= n; i++)

if (flagx[i] == cas-1)

for (int j = 1; j <= n; j++)

if (v[i][j] == -1) {

flagy[j] = cas;

chang = 1;

}

for (int i = 1; i <= n; i++)

if (flagy[i] == cas-1)

for (int j = 1; j <= n; j++)

if (v[j][i] == 1) {

flagx[j] = cas;

chang = 1;

}

}

int Mi = ~0u>>2;

for (int i = 1; i <= n; i++)

for (int j = 1; j <= n; j++)

if (flagx[i] && !flagy[j] && Mi > a[i][j])

Mi = a[i][j]; //未划线找最小的

for (int i = 1; i <= n; i++)

for (int j = 1; j <= n; j++)

if (flagx[i] && !flagy[j]) //未划线

a[i][j] -= Mi;

else if (!flagx[i] && flagy[j]) //线交点

a[i][j] += Mi;

}

}

int main() {

int n,a[N][N],b[N][N];

scanf("%d",&n);

for (int i = 1; i <= n; i++)

for (int j = 1; j <= n; j++) {

scanf("%d",&a[i][j]);

b[i][j] = -a[i][j];

}

printf("%d\n%d",calc(a,n),-calc(b,n));

return 0;

}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值