匈牙利算法的一个实现

本来是编程之美资格赛的一道题,本来想用匈牙利算法解决这道题,后来发现我对题目理解出现偏差。匈牙利算法处理的是nxn的方阵,而题目要求是mxn的矩阵。不管怎样,还是贴出来分享给大家。

时间限制: 2000ms
单点时限: 1000ms
内存限制: 256MB

描述

给你一个m x n (1 <= m, n <= 100)的矩阵A (0<=aij<=10000),要求在矩阵中选择一些数,要求每一行,每一列都至少选到了一个数,使得选出的数的和尽量的小。


输入

多组测试数据。首先是数据组数T

对于每组测试数据,第1行是两个正整数m, n,分别表示矩阵的行数和列数。

接下来的m行,每行n个整数,之间用一个空格分隔,表示矩阵A的元素。


输出

每组数据输出一行,表示选出的数的和的最小值。


数据范围

小数据:1 <= m, n <= 5

大数据:1 <= m, n <= 100



样例输入
2
3 3
1 2 3
3 1 2
2 3 1
5 5
1 2 3 4 5
5 1 2 3 4
4 5 1 2 3
3 4 5 1 2
2 3 4 5 1
样例输出
Case 1: 3
Case 2: 5


#include <stdio.h>



void main()
{
int T,m,n,i,j,k,M[100][100],N[101][101],temp,col,row,c,r;
char F[101][101];


scanf("%d",&T);


for(i=0;i<T;i++)
{
getchar();
scanf("%d %d",&m,&n);
for(j=0;j<m;j++)
{
getchar();
for(k=0;k<n;k++)
{
scanf("%d",&M[j][k]);
}
}
if(m<n)  //行少于列,补0行
{
for(j=m;j<n;j++)
for(k=0;k<n;k++)
M[j][k] = 0;
m = n;
}


if(m>n) //列少于行,补0列
{
for(j=0;j<m;j++)
for(k=n;k<m;k++)
M[j][k] = 0;
n = m;
}
//1.每一行减去最小数
for(j=0;j<m;j++)
{
temp = M[j][0];
for(k=0;k<n;k++)
{
if(temp>M[j][k])
temp = M[j][k];
}
for(k=0;k<n;k++)
{
N[j][k] = M[j][k] - temp;
}
}
//2.每一列减去最小数
for(j=0;j<n;j++)
{
temp = N[0][j];
for(k=0;k<m;k++)
{
if(temp>N[k][j])
temp = N[k][j];
}
for(k=0;k<m;k++)
{
N[k][j] = N[k][j] - temp;
}
}

while(1)
{
//3.找出不同行、不同列的0元素
for(j=0;j<=m;j++)
{
for(k=0;k<=n;k++)
{
F[j][k] = 0;
}
}
           while(1)
{
for(j=0;j<m;j++)
N[j][n] = 0;
for(j=0;j<n;j++)
N[m][j] = 0;
temp = 0;
for(j=0;j<m;j++)
{
for(k=0;k<n;k++)
{
if(F[j][n]==0&&F[m][k]==0&&N[j][k]==0)
{
N[m][k]++;
N[j][n]++;
temp++;
}
}
}
if(temp==0)     //如果没有0元素,则停止
break; 
row = 101;
r = 0;
for(j=0;j<m;j++)
{
if(F[j][n]==0&&row>N[j][n])
{
r = j;
row = N[j][n];
}
}
col = 101;
c = 0;
for(j=0;j<n;j++)
{
if(F[m][j]==0&&col>N[m][j])
{
c = j;
col = N[m][j];
}
}
if(row<=col)
{
for(j=0;j<n;j++)
{
if(F[m][j]==0&&N[r][j]==0)
{
if(row>0)
{
F[r][j] = 1;                //交叉元素标记
F[r][n] = 1;                //行划横线
F[m][j] = 1;                //列划横线
row = 0;
for(k=0;k<m;k++)
{
if(N[k][j]==0&&F[k][j]==0)  //同列0元素标记
F[k][j] = 2;
if(N[k][j]==0&&N[k][n]==1)  //独0行划横线
F[k][n] = 1;
}
}
else
{
F[r][j] = 2;
if(N[m][j]==1)                  //独0列划横线
F[m][j] = 1;
}
}
}
}
else
{
for(j=0;j<m;j++)
{
if(F[j][n]==0&&N[j][c]==0)
{
if(col>0)
{
F[j][c] = 1;                //交叉元素标记
F[m][c] = 1;                //列划横线
F[j][n] = 1;                //行划横线
col = 0;
for(k=0;k<n;k++)
{
if(N[j][k]==0&&F[j][k]==0)  //同行0元素标记
F[j][k] = 2;
if(N[j][k]==0&&N[m][k]==1)  //独0列划横线
F[m][k] = 1;
}
}
else
{
F[j][c] = 2;
if(N[j][n]==1)                  //独0行划横线
F[j][n] = 1;
}
}
}
}
}
temp = 0;
for(j=0;j<m;j++)
for(k=0;k<n;k++)
if(F[j][k]==1)
temp++;
if(temp>=m||temp>=n)    //如果独立0元素等于行或列数,结束搜索
break;
//4.作最小数目的直线,覆盖所有0元素
for(j=0;j<m;j++)
F[j][n] = 0;
for(j=0;j<n;j++)
F[m][j] = 0;
for(j=0;j<m;j++)   //标记没有交叉元素的行
{
row = 0;
for(k=0;k<n;k++)
if(F[j][k]==1)
row++;
if(row==0)
F[j][n] = 1;
}
while(1)
{
temp = 0;
for(j=0;j<m;j++)//对没有交叉元素标记的行,对0元素所在列标记
if(F[j][n]==1)
for(k=0;k<n;k++)   
{
if(F[j][k]==2)
{
if(F[m][k]!=1)  //没有被标记,则标记
{
F[m][k] = 1;
temp++;
}
}
}
for(j=0;j<n;j++)  //对标记的列,对交叉元素所在行进行标记
if(F[m][j]==1)
for(k=0;k<m;k++)
{
if(F[k][j]==1)
{
if(F[k][n]!=1)  //没有被标记,则标记
{
F[k][n] = 1;
temp++;
}
}
}
if(temp==0)
break;
}
for(j=0;j<m;j++) //对没有标记的行和标记的列划线
F[j][n] = 1 - F[j][n];
temp = 10001;
//5.产生新的矩阵
for(j=0;j<m;j++)    //找出未被直线覆盖的最小元素
if(F[j][n]==0)
for(k=0;k<n;k++)
if(F[m][k]==0&&temp>N[j][k])
temp = N[j][k];


for(j=0;j<m;j++)    //未被直线覆盖的行减去最小元素
if(F[j][n]==0)
for(k=0;k<n;k++)
N[j][k] -= temp;


for(j=0;j<n;j++)    //被直线覆盖的列加上最小元素
if(F[m][j]==1)
for(k=0;k<m;k++)
N[k][j] += temp;
}//while1
/* for(j=0;j<m;j++)
{
for(k=0;k<n;k++)
{
printf("%d ",F[j][k]);
}
printf("\n");
}*/
temp = 0;
for(j=0;j<m;j++)    
for(k=0;k<n;k++)
if(F[j][k]==1)
temp += M[j][k];
printf("Case %d: %d\n",i+1,temp);
}//for
}//main
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值