【问题描述】
某公司有3个商店A, B, C,拟将新招聘的5名员工分配给这3个商店,各商店得到的新员工后每年的赢利情况如表所示,求如何分配才能使利益最大化?
表
(右边数字是每个公司分配0 – 5个人的收益情况)
公司A:0 3 7 9 12 13
公司B:0 5 10 11 11 11
公司C:0 4 6 11 12 12
【思路】
本题也是动态规划的经典题目,这个题还可以换成投资等其他的实际问题,总之,变化很多。这题一拿到手,我就想将数组dp[i][j]设为前i个公司分配j个员工的能获得的最大收益。这就是直觉。
我说过,dp问题中最难的地方就是设状态,即用一种合理的方式定义dp[i][j]数组的含义,一旦这个定义清楚了,推状态转移方程就不是很难,否则很难拿出合理的状态转移方程!
那么我们可以先手动地填一遍这个dp表,填出的结果如图所示
手动填数组这个过程相当重要,填完之后,你就能找到规律,并且也可以明确遍历顺序和边界条件这一系列的关键要素!比如这题,我填完之后就明确了,边界条件肯定是第一行和第一列,遍历顺序一定是先从左到右,再从上到下,状态转移方程可能稍微要想一想。
比如,dp[2][3]它是怎么算出来等于10的呢?经过分析,我们发现,前两个公司分配3个员工,我们可以1.给A公司分配0个员工,B公司分配3个员工;2.给A分配1个,B分配2个;3.给A分配2个,B分配1个;4.给A分配3个,B分配0个。无非就这四种情况,所以算dp[i][j]的时候,肯定还要一重循环,所以这时候,状态转移方程几乎已经推出来了!然后我们再由特殊到一般就行了
状态转移方程写成数学语言应该是:dp[i][j] = max(dp[i - 1][k] + a[i][j - k])(其中:0 <= k <= j)
代码:
/*
3 5
0 3 7 9 12 13
0 5 10 11 11 11
0 4 6 11 12 12
*/
#include<iostream>
using namespace std;
const int maxn = 100;
int m, n; //m为商店个数,n为员工个数
int dp[maxn][maxn];
int a[maxn][maxn]; //分配员工数和赢利情况的表
int path[maxn][maxn]; //存储路径,记录分配结果
//设dp[i][j]表示,前i个商店分配j个员工能获得的最大利益值
int DP()
{
//先处理边界1
for(int i = 1;i <= m;i++)
{
dp[i][0] = 0;
path[i][0] = 0;
}
//边界2
for(int j = 1;j <= n;j++)
{
dp[1][j] = a[1][j];
path[1][j] = j;
}
//再来其他情况,遍历顺序从上到下,从左到右,是常规顺序
for(int i = 2;i <= m;i++)
{
for(int j = 1;j <= n;j++)
{
int max_res = 0;
int max_j = 0;
for(int k = 0;k <= j;k++) //找出最大的那个
{
if(dp[i - 1][k] + a[i][j - k] > max_res)
{
max_res = dp[i - 1][k] + a[i][j - k];
max_j = j - k; //记录下本商店在最大利益时分配的人,注意,是j - k表示本商店的分配人数!
}
}
dp[i][j] = max_res;
path[i][j] = max_j;
}
}
//打印dp数组
cout << "dp数组为:" << endl;
for(int i = 1;i <= m;i++)
{
for(int j = 0;j <= n;j++)
{
cout << dp[i][j] << " ";
}
cout << endl;
}
cout << endl;
cout << "path数组为:" << endl;
for(int i = 1;i <= m;i++)
{
for(int j = 0;j <= n;j++)
{
cout << path[i][j] << " ";
}
cout << endl;
}
return dp[m][n];
}
int main()
{
cout << "请输入商店个数和员工个数:";
cin >> m >> n;
cout << "请输入各商店分配员工数和赢利情况的表格:" << endl;
for(int i = 1;i <= m;i++)
{
for(int j = 0;j <= n;j++)
{
cin >> a[i][j];
}
}
cout << DP() << "万元" << endl;
//输出具体方案
cout << "具体分配方案如下:" << endl;
cout << m << "号公司分配" << path[m][n] << "个员工" << endl;
int r = n - path[m][n]; //剩余的人数
for(int i = m - 1;i >= 1;i--)
{
cout << i << "号公司分配" << path[i][r] << "个员工" << endl;
r = r - path[i][r];
}
return 0;
}
运行结果: