动态规划是有经典的01书包问题引入
具有“最优子结构”、“子问题重叠”、“边界”和“子问题独立”,当你发现你正在思考的问题具备这四个性质,
通俗的说就是, 你要求解一个问题, 而加入你知道这个问题的子问题的话, 那么你就可以知道你的问题答案,
而这个子问题又跟这个问题相思的求法。
想要更多的了解动态规划,下面是一个例子
有一个国家,所有的国民都非常老实憨厚,某天他们在自己的国家发现了十座金矿, 并且这十座金矿在地图上排成一条直线,国王知道这个消息后非常高兴,他希望能够把这些 金子都挖出来造福国民,首先他把这些金矿按照在地图上的位置从西至东进行编号 ,依次为 0 、1 、2 、3 、4 、5 、6 、7、8 、9 ,然后他命令他的手下去对每一座金矿进行勘测 ,以便知道 挖取每一座金矿需要多少人力以及每座金矿能够挖出多少金子,然后动员国民都来挖金子 。
题目补充 1 :挖每一座金矿需要的人数是固定的,多一个人少一个人都不行。国王 知道每个金矿各需要多少人手,金矿 i 需要的人数为 peopleNeeded[i]。
题目补充2 :每一座金矿所挖出来的金子数是固定的,当第i 座金矿有peopleNeeded[i] 人去挖的话,就一定能恰好挖出 gold[i]个金子。否则一个金子都挖不出来。
题目补充 3 :开采一座金矿的人完成开采工作后,他们不会再次去开采其它金矿, 因此一个人最多只能使用一次。
题目补充 4 :国王在全国范围内仅招募到了 10000 名愿意为了国家去挖金子的人, 因此这些人可能不够把所有的金子都挖出来,但是国王希望挖到的金子越多越好。
题目补充 5 :这个国家的每一个人都很老实(包括国王),不会私吞任何金子,也不 会弄虚作假,不会说谎话。
题目补充 6 :有很多人拿到这个题后的第一反应就是对每一个金矿求出平均每个人 能挖出多少金子,然后从高到低进行选择,这里要强调这种方法是错的,如果你也是这样想 的,请考虑背包模型,当有一个背包的容量为 10,共有 3 个物品,体积分别是 3 、3 、5,价 值分别是 6、6 、9 ,那么你的方法取到的是前两个物品,总价值是 12,但明显最大值是后两 个物品组成的 15。
题目补充 7 :我们只需要知道最多可以挖出多少金子即可,而不用关心哪些金矿挖 哪些金矿不挖。
那么,在只有 10000 个人的情况下最多能挖出多少金子呢?
把问题抽象化就是
输入文件第一行有两个数,第一个是国王可用用来开采金矿的总人数,第二个是总 共发现的金矿数。
输入文件的第 2 至 n+1 行每行有两个数,第 i 行的两个数分别表示第 i -1 个金矿需要 的人数和可以得到的金子数。
输入样例:
100 5
77 92
22 22
29 87
50 46
99 90
输出样例:
133
分析:
国王首先来到了第 9 个金矿的所在地(注意,第9 个就是最后一个,因为是从0 开 始编号的,最西边的那个金矿是第0 个),他的臣子告诉他,如果要挖取第9 个金矿的话就 需要 1500 个人,并且第9 个金矿可以挖出 8888 个金子。
国王说道:“如果我挖取第 9 座金矿的话那么我现在就能获得 8888 个金子,而 我将用去 1500 个人,那么我还剩下 8500 个人。我亲爱的左部下,如果你告诉我当我把所有 剩下的 8500 个人和所有剩下的其它金矿都交给你去开采你最多能给我挖出多少金子的话, 那么我不就知道了在第 9 个金矿一定开采的情况下所能得到的最大金币数吗? ”
国王继续对右部下说到 :“亲爱的右部下,也许我并不打算开采这第 9 座金矿,那么我依然拥有 10000 个人,如果我把这 10000 个人和剩下的金矿都给你的话,你最多能给我挖出多少个金子呢?”
一个一个传递下去,这样一个子问题就变成了父问题。很明显,当被问到给你z 个人和仅有第 0 座金矿时最多能挖出多少金子时,就不需要别人的帮助,因为你知道,如果 z 大于等于挖取第 0 座金矿所需要的人数的话,那么挖出来的最多金子数就是第 0座金矿能够挖出来的金子数,如果这 z 个人不够开采第 0 座金矿,那么能挖出来的最多金子数就是 0,因为这唯一的金矿不够人力去开采。
当 mineNum = 0 且 people >= peopleNeeded[mineNum] 时 f(people,mineNum) = gold[mineNum]
当 mineNum = 0 且people < peopleNeeded[mineNum]时 f(people,mineNum) = 0
mineNum!= 0 时
f(people,mineNum) =f(people-peopleNeeded[mineNum], mineNum-1) + gold[mineNum]与f(people, mineNum-1)中的较大者,
前两个式子对应动态规划 的“边界”,后一个式子对应动态规划的“最优子结构”
下面是代码
#include <iostream>
using namespace std;
const int max_n = 100; //程序支持的最多金矿数
const int max_people = 10000; //程序支持的最多人数
int n; //金矿数
int peopleTotal; //可以用于挖金子的人数
int peopleNeed[max_n]; //每座金矿需要的人数
int gold[max_n]; //每座金矿能够挖出来的金子数
//maxGold[i][j] 保存了i个人挖前j个金矿能够得到的最大金子数,等于- 1 时表示未知
int maxGold[max_people][max_n];
int max(int a, int b)
{
return a > b ? a : b;
}
/*
获得在仅有 people 个人和前 mineNum 个金矿时能够得到的最大金子数,
注意"前多少个" 也是从 0 开始编号的
*/
int GetMaxGold(int people, int mineNum){
int retMaxGold; //申明返回的最大金子数
//如果这个问题曾经计算过 [ 对应动态规划中的"做备忘录"]
if(maxGold[people][mineNum] != -1){
retMaxGold = maxGold[people][mineNum]; //获得保存起来的值
}
else if(mineNum == 0) //如果仅有一个金矿时 [ 对应动态规划中的"边界"]
{
if(people >= peopleNeed[mineNum]) //当给出的人数足够开采这座金矿
retMaxGold = gold[mineNum]; //得到的最大值就是这座金矿的金子数
else //否则这唯一的一座金矿也不能开采
retMaxGold = 0; //得到的最大值为 0 个金子
}
else if(people >= peopleNeed[mineNum]) // 如果人够开采这座金矿[对应动态规划中的"最优子结构"]
{
//考虑开采与不开采两种情况,取最大值
retMaxGold = max(
GetMaxGold(people - peopleNeed[mineNum],mineNum - 1) + gold[mineNum],
GetMaxGold(people,mineNum - 1)
);
}
else//否则给出的人不够开采这座金矿 [ 对应动态规划中的"最优子结构"]
{
retMaxGold = GetMaxGold(people,mineNum - 1); //仅考虑不开采的情况}//做备忘录
maxGold[people][mineNum] = retMaxGold;
}
return retMaxGold;
}
int main(){
cin>>peopleTotal>>n;
if(peopleTotal > max_n){
cout<<"程序支持的最多人数"<<max_n<<endl;
return 0;
}
int i;
for(i = 0; i < n; i++)
cin>>peopleNeed[i] >> gold[i];
memset(maxGold, -1, sizeof(maxGold));
cout<<GetMaxGold( peopleTotal, n - 1)<<endl;
return 0;
}
以上内容观自SDJL 网友的书《动态规划》, 其个人的博客这里给个链接www.cnblogs.com/sdjl
最后下面是一个关于动态规划的回答, 受到一致好评于是截屏, 以供后用
现在2012年11月24星期六
我终于懂得了一部分上面那个图(总还会有不知道的地方), 我写完动态规划代码后, 觉得跟fire Net(这个日志里也有http://blog.csdn.net/fofu33/article/details/8213893)的深度优先代码怎么如此相似。这就是那句:"能用动态规划解决的问题",
一定能用搜索解决。
于是今天为了巩固我又从打了一遍代码, 写完fire Net 写这个时候, 不知不觉就写成了fire Net的类型。 代码如下
#include "iostream" using namespace std; #define maxlen 100 //支持的最大金矿数 int peopleTotal; //给的总人数 int kuang; //金矿的个数 int goldmax; //存储最后的黄金数 int gold[maxlen]; //存储个每座金矿所能得到的金子数 int peopleNeed[maxlen]; //每座金矿所需要的人 void maxgold(int people, int num, int goldTotal){ //num 存储的是到第几座矿了, num 从0到kuang if( num == kuang){ if(people > peopleNeed[num]){ //最后一座矿如果人数大于所需, 那得挖啊 goldTotal += gold[num]; } if( goldTotal > goldmax ) goldmax = goldTotal; return; // 原来忘了写这句, 就导致越界 } if(people > peopleNeed[num]){ //人数大于所需要的人数时 int alt = people - peopleNeed[num]; maxgold(alt, num+1, goldTotal + gold[num]); //挖 } maxgold(people, num+1, goldTotal); //人数大于所需人数 或少于, 不挖的情况 } int main(){ while( cin >> peopleTotal >> kuang && peopleTotal && kuang){ goldmax = 0; int i; for(i = 0; i < kuang; i++){ cin >> peopleNeed[i] >> gold[i]; } maxgold(peopleTotal, 0, 0); cout<<goldmax<<endl; } return 0; }
这段程序没有记忆功能, 让我们给他加记忆功能, 这样就使得时间减小
#include "iostream" using namespace std; #define maxlen 100 //支持的最大金矿数 #define peoplemax 10000 int peopleTotal; //给的总人数 int kuang; //金矿的个数 int goldmax; //存储最后的黄金数 int gold[maxlen]; //存储个每座金矿所能得到的金子数 int peopleNeed[maxlen]; //每座金矿所需要的人 int maxGold[peoplemax][maxlen]; //存储在人数为i,金矿数到j时可获得的金币数, 这也就是所谓的记忆功能 int maxgold(int people, int num, int goldTotal){ //num 存储的是到第几座矿了, num 从0到kuang if( maxGold[people][num] != -1){ goldTotal = maxGold[people][num]; return goldmax; } if( num == kuang){ if(people > peopleNeed[num]){ //最后一座矿如果人数大于所需, 那得挖啊 goldTotal += gold[num]; } if( goldTotal > goldmax ) goldmax = goldTotal; return goldmax; // 原来忘了写这句, 就导致越界 } if(people > peopleNeed[num]){ //人数大于所需要的人数时 int alt = people - peopleNeed[num]; maxGold[people][num] = maxgold(alt, num+1, goldTotal + gold[num]); //挖 } maxGold[people][num] = maxgold(people, num+1, goldTotal); //人数大于所需人数 或少于, 不挖的情况 return goldmax; } int main(){ while( cin >> peopleTotal >> kuang && peopleTotal && kuang){ int i, j; for(i = 0; i < peoplemax; i++){ for(j = 0; j < maxlen; j++) maxGold[i][j] = -1; } goldmax = 0; for(i = 0; i < kuang; i++){ cin >> peopleNeed[i] >> gold[i]; } maxgold(peopleTotal, 0, 0); cout<<goldmax<<endl; } return 0; }