动态规划的题有:
- 背包问题
- 递推
- 公共子序列
- 最长递增子序列--LIS问题
第一题: 01背包问题
description
现有一笔经费可以报销一定额度的发票。允许报销的发票类型包括买图书(A类)、文具(B类)、差旅(C类),要求每张发票的总额不得超过1000元,每张发票上,单项物品的价值不得超过600元。现请你编写程序,在给出的一堆发票中找出可以报销的、不超过给定额度的最大报销额。
Input
测试输入包含若干测试用例。每个测试用例的第1行包含两个正数 Q 和 N,其中 Q 是给定的报销额度,N(<=30)是发票张数。随后是 N 行输入,每行的格式为:
m Type_1:price_1 Type_2:price_2 ... Type_m:price_m
其中正整数 m 是这张发票上所开物品的件数,Type_i 和 price_i 是第 i 项物品的种类和价值。物品种类用一个大写英文字母表示。当N为0时,全部输入结束,相应的结果不要输出。
Output
对每个测试用例输出1行,即可以报销的最大数额,精确到小数点后2位。
Sample Input
200.00 3
2 A:23.50 B:100.00
1 C:650.00
3 A:59.99 A:120.00 X:10.00
1200.00 2
2 B:600.00 A:400.00
1 C:200.50
1200.50 3
2 B:600.00 A:400.00
1 C:200.50
1 A:100.00
100.00 0
Sample Output
123.50
1000.00
1200.50
简单的01背包问题,复杂在数据的舍去与输入
题目理解错了,他是说有n张发票,给出每张发票的消息,你要自己求出有效的票
而每张发票里,有多组数据,所以应该分别计算a,b,c的总价格
ACcode
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
static const int MAX = 1000*30*100+5;//30张1000元的发票(扩大100倍)
int d[MAX];//合法的票
int dp[MAX];
int cnt;//合法票的数量
float q;//背包大小
double DP(){
int maxx=(int)(q*100);
for(int i=0;i<cnt;i++){
for(int j=maxx;j>=d[i];j--){
dp[j]=max(dp[j],dp[j-d[i]]+d[i]);
}
}
return dp[maxx]/100.0;
}
int main(){
int n;
while(scanf("%f%d",&q,&n),n){
cnt=0;
memset(dp,0,sizeof(dp));
memset(d,0,sizeof(d));
while(n--){
//初始化
int flag=0;
int m;
scanf("%d",&m);
float suma=0,sumb=0,sumc=0;
for(int i=0;i<m;i++){
char who;
float x;
scanf(" %c:%f",&who,&x);
if(who=='A'){
suma+=x;
}else if(who=='B'){
sumb+=x;
}else if(who=='C'){
sumc+=x;
}else{
flag=1;
}
}
if((suma+sumb+sumc)<=1000&&flag==0&&suma<=600&&sumb<=600&&sumc<=600){
for(int i=0;i<m;i++){
d[cnt++]=(int)((suma+sumb+sumc)*100);
//printf("%d ",d[0]);
}
}
}
printf("%.2lf\n",DP());
}
return 0;
}
类似动态规划在斐波那契数列中的应用,对于数大的情况,需要不断地调用数小时的情况,那么就可以构建一个数组,使得每次的调用复杂度为O(1)即可。
经典dp问题,从最上面一直加到最下面,从起点起,到终点,寻找一条路使得值最大或最小。
第二题:递推
HDU2084 数塔
Problem Description
在讲述DP算法的时候,一个经典的例子就是数塔问题,它是这样描述的:
有如下所示的数塔,要求从顶层走到底层,若每一步只能走到相邻的结点,则经过的结点的数字之和最大是多少?
已经告诉你了,这是个DP的题目,你能AC吗?
Input
输入数据首先包括一个整数C,表示测试实例的个数,每个测试实例的第一行是一个整数N(1 <= N <= 100),表示数塔的高度,接下来用N行数字表示数塔,其中第i行有个i个整数,且所有的整数均在区间[0,99]内。
Output
对于每个测试实例,输出可能得到的最大和,每个实例的输出占一行。
Sample Input
1
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
Sample Output
30
ACcode
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int map[105][105];
void dp(int i,int j){
//最左边
if(j==0)map[i][0]=map[i][0]+map[i-1][0];
//最右边
else if(i==j)map[i][j]=map[i][j]+map[i-1][j-1];
//在中间的情况
else map[i][j]=max(map[i-1][j-1],map[i-1][j])+map[i][j];
}
int main(){
int c;
cin>>c;
while(c--){
int n;
cin>>n;
for(int i=0;i<n;i++){
for(int j=0;j<=i;j++){
scanf("%d",&map[i][j]);
}
}
for(int i=1;i<n;i++){
for(int j=0;j<=i;j++){
dp(i,j);
}
}
int maxx=0;
for(int i=0;i<n;i++){
maxx=max(map[n-1][i],maxx);
}
printf("%d\n",maxx);
}
return 0;
}
对于Runtime Error(ACCESS_VIOLATION)
很有可能是数组越界了,就是你定义的数组不够大,回题目看看范围
之所以可以用二维数组的原因时他的大小不是nn,而时n10;
我一直runtime的原因就是因为数组大小不过,少了个0
还要wrong的原因是因为人最开始是从x=5开始的,
所以时间为1时只有x=4,x=5,x=6才有效
如果其他位置在时间为1时有数字,时无效的
第三题: 递推
HDU1176 免费馅饼
Problem Description
都说天上不会掉馅饼,但有一天gameboy正走在回家的小径上,忽然天上掉下大把大把的馅饼。说来gameboy的人品实在是太好了,这馅饼别处都不掉,就掉落在他身旁的10米范围内。馅饼如果掉在了地上当然就不能吃了,所以gameboy马上卸下身上的背包去接。但由于小径两侧都不能站人,所以他只能在小径上接。由于gameboy平时老呆在房间里玩游戏,虽然在游戏中是个身手敏捷的高手,但在现实中运动神经特别迟钝,每秒种只有在移动不超过一米的范围内接住坠落的馅饼。现在给这条小径如图标上坐标:
为了使问题简化,假设在接下来的一段时间里,馅饼都掉落在0-10这11个位置。开始时gameboy站在5这个位置,因此在第一秒,他只能接到4,5,6这三个位置中其中一个位置上的馅饼。问gameboy最多可能接到多少个馅饼?(假设他的背包可以容纳无穷多个馅饼)
Input
输入数据有多组。每组数据的第一行为以正整数n(0<n<100000),表示有n个馅饼掉在这条小径上。在结下来的n行中,每行有两个整数x,T(0<T<100000),表示在第T秒有一个馅饼掉在x点上。同一秒钟在同一点上可能掉下多个馅饼。n=0时输入结束。
Output
每一组输入数据对应一行输出。输出一个整数m,表示gameboy最多可能接到m个馅饼。
提示:本题的输入数据量比较大,建议用scanf读入,用cin可能会超时。
Sample Input
6
5 1
4 1
6 1
7 2
7 2
8 3
0
Sample Output
4
ACcode
------
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int dp[100005][12];
int a[100005][12];
int main(){
int n;
while(scanf("%d",&n)&&n){
//int dp[10005][12],a[10005][12];
memset(a,0,sizeof(a));
memset(dp,0,sizeof(dp));
int maxt=0;
//a[][]数组时间位置对应的馅饼数目
for(int i=0;i<n;++i){
int x,t;
scanf("%d%d",&x,&t);
//找到最长时间,减少时间复杂度
maxt=max(t,maxt);
a[t][x]++;
}
//坑 最开始的位置就在5
dp[1][4]=a[1][4];
dp[1][5]=a[1][5];
dp[1][6]=a[1][6];
//状态转移方程
for(int i=2;i<=maxt;i++){
for(int j=0;j<=10;j++){
if(j==0){
dp[i][j]=max(dp[i-1][j],dp[i-1][j+1]);
}else if(j==10){
dp[i][j]=max(dp[i-1][j-1],dp[i-1][j]);
}else{
dp[i][j]=max(dp[i-1][j-1],max(dp[i-1][j],dp[i-1][j+1]));
}
//再加上增加的数
dp[i][j]+=a[i][j];
}
}
//在最后一行,即最大时刻时,寻找最大的数
int maxn=-1;
for(int i=0;i<=10;i++){
maxn=max(maxn,dp[maxt][i]);
}
printf("%d\n",maxn);
}
return 0;
}
第四题: 01背包问题
HUD2602 Bone Collector
经典01背包问题
以下分别利用一维或二维数组实现
其中w是最大背包容量,
数组w[]是物体的重量或体积
数组v[]是物体的价值
dp[i][w]表示当背包容量为w时,对i前面几个物体进行求最值后的最大价值
memset()函数的头文件是cstring或string.h
memset()只能初始化0和-1
Problem Description
Many years ago , in Teddy’s hometown there was a man who was called “Bone Collector”. This man like to collect varies of bones , such as dog’s , cow’s , also he went to the grave …
The bone collector had a big bag with a volume of V ,and along his trip of collecting there are a lot of bones , obviously , different bone has different value and different volume, now given the each bone’s value along his trip , can you calculate out the maximum of the total value the bone collector can get ?
Input
The first line contain a integer T , the number of cases.
Followed by T cases , each case three lines , the first line contain two integer N , V, (N <= 1000 , V <= 1000 )representing the number of bones and the volume of his bag. And the second line contain N integers representing the value of each bone. The third line contain N integers representing the volume of each bone.
Output
One integer per line representing the maximum of the total value (this number will be less than 231).
Sample Input
1
5 10
1 2 3 4 5
5 4 3 2 1
Sample Output
14
用贪心算法做不了这题
为什么不能用贪心算法,求出最大每个物品单位体积的价值,排序去最大的几个呢?
因为对于不同的物品,价值不同,而且重量也不同
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int main(){
int t;
cin>>t;
while(t--){
int n,m;
cin>>n>>m;
int v[10005],w[10005],dp[10005];
//memset函数
//memset函数在string头文件里面
memset(v,0,sizeof(v));
memset(w,0,sizeof(w));
memset(dp,0,sizeof(dp));
//value
for(int i=0;i<n;i++){
scanf("%d",&v[i]);
}
//vol
for(int i=0;i<n;i++){
scanf("%d",&w[i]);
}
for(int i=0;i<n;i++){
for(int j=m;j>=w[i];j--){
//如果空间为j的背包放得下重量为w[i]的物品
if(j>=w[i]){
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}
}
printf("%d\n",dp[m]);
}
return 0;
}
/**省略了放不下的情况,因为放不下这个物体的话,
dp[i][j]==dp[i-1][j],所以可以省略
//变化的只是那些放得下的时候,而且
dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]+v[i]);
所以得到以下的式子
**/
背包问题的二维数组方法
//价值输入
for(int i=1;i<=n;i++){
scanf("%d",&v[i]);
}
//重量输入
for(int i=1;i<=n;i++){
scanf("%d",&w[i]);
}
//i是对于重量w的下标进行循环,对每个w进行尝试
for(int i=1;i<=n;i++){
//j时对于背包容量进行循环,对于每个背包容量小于总容量的背包进行尝试
for(int j=1;j<=w;j++){
//如果容量大于w[i],即当前容量大于当前判断的重量,这个重量的背包可以放进去
if(w[i]<j){
if(dp[i-1][j-w[i]+v[i]>dp[i-1][j]){
dp[i][j]=dp[i-1][j-w[i]+v[i]];
}else{
dp[i][j]=dp[i-1][j];
}
}else{
dp[i][j]=dp[i-1][j];
}
}
}
/**
此处又有两种情况,把这个物品放入背包以及放了该物品后(当前物品占用了背包空间)
剩余容量用来放前一个物品时的价值大于不放这个物品时该容量的价值(容量与当前容量相同,
但是,本来可以放该物品,但现在不放该物品,而是把空间腾出来放之前的那些物品)
**/
//上面可以写成
//dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
//当前物品的重量太大,不能放进去背包,那么当前情况的价值为上一个物品放入背包的价值