动态规划--国王和金矿
动态规划--国王和金矿
封面图片来自程序员小灰 漫画:什么是动态规划?(整合版), 最近阅读了小灰的系列文章,感觉动态规划这篇讲的挺好的,循序渐进;动态规划这块我是没学透,简单的还好些,一旦题中的变量大于1时,就开始懵逼了。比较纠结的是如下几个问题:
- 动态规划与排列组合的关系
- 动态规划与递归的关系
- 怎样才能找出最优子结构,并写出状态转移式(递归式)
动态规划的几个要素为:
- 最优子结构
- 状态转移公式
- 边界
下面通过分析国王和金矿这个题目来体会一下。
题目:有一个国家发现了5座金矿,每座金矿的黄金储量不同,需要参与挖掘的工人数也不同。参与挖矿工人的总数是10人。每座金矿要么全挖,要么不挖,不能派出一半人挖取一半金矿。要求用程序求解出,要想得到尽可能多的黄金,应该选择挖取哪几座金矿?
(注:该问题也是背包问题的另一种形式)
方法一:每个金矿要么挖要么不挖,共有2^5组合方法。通过枚举这些组合,选出最大产出的组合。组合方式可用一棵树示意。

(注:绿色表示挖,灰色表示不挖)
自顶向下进行遍历分支(从根部到叶子的路径为一种组合情况),算出分支的人力和产出,得出最大的产出那种组合。
源码如下:
// 使用排列组合方法(通过递归枚举)找出最优解
//
#include <stdio.h>
#include <stdlib.h>
#define MAX_MINES 5
// 金矿信息
struct FGoldMineMeta
{
int Golds; // 金量
int Workers;// 工人
};
// 递归遍历的Context
struct FTravelContext
{
bool BestCombination[MAX_MINES];
int BestOutputVal;
bool TravelCombination[MAX_MINES];
int TravelOutputVal;
int RemainWorkerNum;
FTravelContext(int InWorkerNum)
: BestOutputVal(0)
, TravelOutputVal(0)
, RemainWorkerNum(InWorkerNum)
{
for (int k = 0; k < MAX_MINES; k++)
{
BestCombination[k] = TravelCombination[k] = false;
}
}
};
void RecursiveCombination(const FGoldMineMeta InMines[MAX_MINES], FTravelContext &InCtx, const int InDepth, const bool InbDig)
{
bool SavedDig = InCtx.TravelCombination[InDepth];
int SavedTravelOutputVal = InCtx.TravelOutputVal;
int SavedRemainWorkerNum = InCtx.RemainWorkerNum;
bool bStopTravel = false;
InCtx.TravelCombination[InDepth] = InbDig;
if (InbDig)
{
const int WorkerNum = InMines[InDepth].Workers;
const int GoldNum = InMines[InDepth].Golds;
if (InCtx.RemainWorkerNum >= WorkerNum)
{
InCtx.TravelOutputVal += GoldNum;
InCtx.RemainWorkerNum -= WorkerNum;
}
else
{
bStopTravel = true;
}
}
bStopTravel = bStopTravel || (InDepth == 0);
if (bStopTravel)
{
// 进行结算
if (InCtx.TravelOutputVal > InCtx.BestOutputVal)