题目来源:NUISTOJ
P1439 背包九讲(1):简单的0-1背包
题目描述:
有一个箱子容量为 V(正整数,0<=V<=20000),同时有 n 个物品(0<n<=30),每个物品有一定的体积和价值。要求 n 个物品中,任取若干个装入箱内,在箱子能放得下的前提下,满足箱子内部的价值最大。
输入描述:
一个整数 v,表示箱子容量
一个整数 n,表示有 n 个物品
接下来 n 个整数,分别表示这 n 个物品的各自体积和价值
输出描述:
一个整数,表示箱子能装下的最大价值。
样例输入:
3
2
2 100
4 200
样例输出:
100
#include<iostream>
using namespace std;
int dpfun(int num, int capacity, int volume[], int value[]){
//num(物品序号)小于0说明没有物品,无法再放
if(num<0) return 0;
//如果放入当前物品,会导致箱子容量小于0,则不放入当前物品(直接进入下一层递归)
if(capacity-volume[num]<0) return dpfun(num-1, capacity, volume, value);
//如果有足够容量来放入当前物品,则判断:
//1、不放入当前物品(直接进入下一层递归);2、放入当前物品(减容量,加价值,进入下一层递归)
//1与2递归的结果取最大值
else return max(dpfun(num-1, capacity, volume, value), value[num]+dpfun(num-1, capacity-volume[num], volume, value));
}
int main()
{
int V, n, volume[50], value[50];
while(cin>>V>>n){
//物品序号从0~n-1
for(int i=0;i<n;i++)
cin>>volume[i]>>value[i];
cout<<dpfun(n-1, V, volume, value)<<endl;//此处物品序号从n-1开始
}
return 0;
}
P1236 夺取宝藏
题目描述:
Ipomy 现在来到了阿兹特克宝藏堆中。这些宝藏散落放在一个 m * n 的网格上,每个宝藏都有一个价值。Ipomy 自然是希望将所有宝藏统统拿走,但他在走出迷宫时,不小心中了魔咒,一次只能向下或向右移动一步。假设 Ipomy 身处网格的左上角,而古城的出口在右下角,他想在离开古城前,拿到价值之和尽可能大的宝藏。请你编写程序,帮助他计算他可以拿到的最大价值之和。
输入描述:
多组输入。
每组输入的第一行为两个整数 m 和 n,用来描述网格的规格。保证 1 <= m, n <= 1000。
接下来的 m 行,每行 n 个整数,表示每个格子上面的宝藏的价值。输入数据保证 Ipomy 起始所在处没有宝藏,即价值为 0,以及每个宝藏的价值均在 int 型的表示范围内。
输出描述:
多组输出。
每组输出占据一行,为一个整数,表示最大的价值之和。
样例输入:
3 4
0 5 2 3
4 5 6 7
8 9 10 11
样例输出:
42
#include<iostream>
using namespace std;
int a[1001][1001], b[1001][1001];
int fun(int a[][1001], int b[][1001], int m, int n, int x, int y){
//以空间换时间
if(b[x][y]>0) return b[x][y];
//移动至出口
if(x==m-1&&y==n-1) return 0;
//若移至最后一行,则只能向右走
if(x==m-1) return b[x][y]=fun(a, b, m, n, x, y+1)+a[x][y+1];
//若移至最后一列,则只能向下走
if(y==n-1) return b[x][y]=fun(a, b, m, n, x+1, y)+a[x+1][y];
//如果可以向下或向右走,则判断:
//1、向下走(进入下一层递归,并加上本次向下走得到的宝藏价值)
//2、向右走(进入下一层递归,并加上本次向右走得到的宝藏价值)
//1与2递归的结果取最大值
return b[x][y]=max(fun(a, b, m, n, x+1, y)+a[x+1][y], fun(a, b, m, n, x, y+1)+a[x][y+1]);
}
int main()
{
int m, n;
while(cin>>m>>n){
for(int i=0;i<m;i++)
for(int j=0;j<n;j++){
cin>>a[i][j];
b[i][j]=-1;//给b赋值-1,便于递归函数中的判断
}
cout<<fun(a, b, m, n, 0, 0)<<endl;
}
return 0;
}
两题求的分别是所放箱子和获得宝藏的最大价值,且分为多步过程。
由于采用动态规划算法思想(将求大问题最优解,分解为求子问题的最优解),所以需要问题分解。
既然涉及到问题的分解,且子问题与原问题求解过程一致,改变的又仅仅是数据量。因此自然想到递归,但递归最大的缺陷是耗时。
耗时的原因是:在各个子问题中,存在相同的求解过程,有大量的重复计算,累计下来的时间是惊人的,所以需要避免重复。那么如果在第一次的求解中就将结果记录下来,当下次遇到重复的问题时,能直接取用已经记录的结果,以避免重复计算,也就是“以空间换时间”。
所以基本思想:递归+数据记录
在dp的简单应用中,问题一般分解为两种,如题一中的“放”与“不放”,题二中的“向下”与“向右”,而判断的,就是在某一步中哪个分解方式更优。
需要采用层层递归深入,最后回归每一步中的最优解,得到大问题的最优解,再结合记录的数据缩减运行时间。
具体数据记录方式:在递归返回结果时,一并存下数据。并将已经记录的数据值作为“递归出口”之一,当再次使用,就无需递归,直接返回记录即可。
(题一是小规模的简单0-1背包问题,因此本题中只用了递归;而题二无论规模,还是重复计算量,都避免不了数据记录)