经典DP问题系列之三:求解资源分配问题(带路径存储)

【问题描述】
某公司有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;
}

运行结果:
在这里插入图片描述

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值