工作分配问题
Description
设有n件工作分配给n个人。将工作i分配给第j个人所需的费用为 cij。试设计一个算法,为每一个人都分配1 件不同的工作,并使总费用达到最小。
设计一个算法,对于给定的工作费用,计算最佳工作分配方案,使总费用达到最小。
Input
输入数据的第一行有1 个正整数n (1≤n≤20)。接下来的n行,每行n个数,表示工作费用。
Output
将计算出的最小总费用输出。
Sample
Input
3 10 2 3 2 3 4 3 4 5
Output
9
解题思路:
本题有n件工作分配给n个人,第一个人有n个选择,第二个人就只有n-1个选择了,第三个人就只有n-2个选择了,所以总共会n的阶乘次判断(n!)
以每个人为一层节点建立一个多叉树,深度搜索dfs该多叉树 就是 对于每个人分配一份工作,
然后用sum记录该遍历过程(该分配工作的过程)产生的费用,
设置一个min_sum记录所有分配过程产生的 最小分配费用,
每次dfs函数遍历到n,即分配到最后一个人的工作时,表示分配完成, 判断此时sum 和 min_sum 的大小,若sum小,则更新min_sum。
遍历过程中,只有当 未分配到第n个人(最后一个人) 并且 sum还小于min_sum 时,才会向深层遍历(给下一个人分配工作),
因为如果 未分配到最后一个人时就已经费用sum大于min_sum了,再往下遍历也没意义, 因为我们要找最小费用,此时已经大于我们之前找到的最小费用了。
这个条件相当于“剪枝”。
当dfs遍历到n(最后一个人)时,我们需要返回上层,给上层的人分配其他工作,尝试搜索更小的费用,
因此,我们需要将刚才分配方式的工作收回,用来尝试别的分配方式,
所以设置一个vis[]数组,vis[j]记录第j个工作是否被分配,被分配则置1,
在dfs遍历到n后,返回上层时,将vis[j]置0,并且将刚才分配方式的sum 减去 arr[pos][j](arr[pos][j]表示第pos个人做第j件工作的费用)
参考:
https://www.cnblogs.com/chenhanwu/p/10148476.html
#include <iostream>
#include <cstdio>/// scanf() printf()
using namespace std;
/***
参考:
https://www.cnblogs.com/chenhanwu/p/10148476.html
***/
int arr[25][25];///记录将工作i分配给第j个人所需的费用为 cij
int vis[25];///标记第i个工作是否被选取,是则置1
int n;///设有n件工作分配给n个人
int sum;///记录当前分配方式的总费用
int min_sum;///记录保存下来的最小费用
void dfs(int pos);///搜索函数,参数为第pos个人
int main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{///i 和 j 都从 1开始记录,到n ,方便理解
for(int j = 1; j <= n; j++)
{///本题中的i和j的for循环都是从1到n,千万别忘了 <= n,否则遍历不到n
scanf("%d", &arr[i][j]);
}
}
sum = 0;///初始sum置0
min_sum = 0x3f3f3f3f;///初始min_sum置为一个很大的值
dfs(1);///从第一个人开始搜索
cout << min_sum << endl;
return 0;
}
void dfs(int pos)
{
if(pos > n)
{///若所有人的工作都已分配完成
if(sum < min_sum)
{///并且该组分配方式的费用小于之前记录的最小费用
min_sum = sum;///则更新最小费用min_sum
}
}
if(sum > min_sum)
{///如果该组分配方式产生的费用大于之前记录的最小费用,
return;///则不向深层遍历,相当于剪枝
}
for(int j = 1; j <= n; j++)///j表示第j件工作
{///这里j从1到n,千万别忘记 j <= n,否则遍历到最后的j = n时会直接跳出来
if(vis[j] == 0)///如果第j件工作未被选取
{
vis[j] = 1;///标记为第j件工作被选取
///更新当前分配方式的总费用
sum += arr[pos][j];///arr[pos][j]表示第pos个人做第j件工作的费用
dfs(pos + 1);///继续向深层遍历
///当前分配方式遍历结束后,向上回溯,搜索其他分配结果
sum -= arr[pos][j];
vis[j] = 0;
}
}
}