动态规划和背包dp问题

来源:互联网 发布:去除淘宝店铺模块缝隙 编辑:程序博客网 时间:2024/06/05 14:30

10.3(第一天):

今天是国庆节假期的第三天了,回顾开学来这近一个月,可用两字概括:迷茫。

野心勃勃地想要飞速提升自我,却因每天的课程安排而无法挪出长时间段自行学习,又不满足于仅听课学习。

选了英语写作课和口语课,突觉以前自己引以为豪的英语其实比起同龄人来说落后不止一点,之前的六级成绩也让我有些失落(但并未觉得很难受,不知是积极乐观还是好强心磨灭的体现)。

想要尽快找到能让自己感到骄傲的东西,把念头转向自己的专业,报名蓝桥杯,希望自己能冲个国奖,保研到理想学校去。

但我是算法小白,我必须超前努力。

因为高考失利,我反思到自己的所谓“踏实”其实是没有自己想法、方法地死读书,缺少灵活安排时间和专注学习的能力和毅力。

焦虑的我,在国庆假期第三天,打开b站,没有再搜游戏视频,反而一念之间搜了“算法”,发现有自己高考填志愿在海大之前却连调档线都没能上的杭电,和高考后望尘莫及的电子科大,它们都有oj,而正有两个人在b站上开视频在讲oj上的部分算法题。

我知道,这些视频,是我算法之路的开始。


我打算:只要有空闲时间就去图书馆安排看视频学习,学了是一定要在这来码一遍做好笔记的,周一周二的水晚课就下载视频到手机里离线看(回来再补笔记)。每天搞懂一个算法,做懂例题并码好,我相信终有一日成大牛。

持续21天,据说可以成为习惯,你试一试呢。

我必须让自己感到“不舒适”,而不要去在意他人的眼光。我就是我,我要走出我勤勉、坚持的算法之路。


【动态规划 DP】:

1.数塔问题

#include <stdio.h>#include <bits/stdc++.h>#include <algorithm>
using namespace std;int main(){int t,n,dp[105][105],a[105][105]; //dp用来储存结果,a来代表该格子的数    scanf("%d",&t);    //待测实例数while(t--)      {scanf("%d",&n);    //该实例中有几层for(int i=1;i<=n;i++)for(int j=1;j<=i;j++)scanf("%d",a[i][j]);    //输入每层的数据}memset(dp,0,sizeof(dp));     for(int i=1;i<=n;i++)dp[n][i]=a[n][i];   //最后一层的dp就是自己的值for(int i=n-1;i>=1;i--) //注意!要自底向上,用数组储存。  for(int j=1;j<=i;j++)  dp[i][j]=max(dp[i+1][j],dp[i+1][j+1]); return 0;}


2.馅饼问题


#include <stdio.h>#include <bits/stdc++.h>#include <algorithm>using namespace std;int main(){int n,m,x,t,dp[10005][13]; //n是共有n个馅饼掉下来,m记录最多是掉了多少秒,x是位置,t是掉落时间点,dp是共接馅饼数。while(--scanf("%d",&n)&&n){memset(dp,0,sizeof(dp));m=0;while(n--){scanf("&d&d",&x,&t)if(t>m) m=t;dp[t][x]++          //先把这些点掉来的馅饼数初始化好。}}for(int i=m-1;i>=1;i--)for(int j=0;j<=10;j++)dp[i][j]=max(max(dp[i+1][j-1],dp[i+1][j]),dp[i+1][j+1]);  //用dp,同理倒推,此时的i为开始计算dp的时间节点}printf("%d\n",dp[0][5]);}



3.畅通工程续:

#include <stdio.h>#include <bits/stdc++.h>#include <algorithm>using namespace std;const int maxn=205;int n,m;int mp[maxn][maxn];int main(){while(scanf("%d%d",&n,&m)!=EOF){for(int i=0;i<n;i++)for(int j=0;j<n;j++)if(i==j) mp[i][j]==0;else mp[i][j]=1e6;  //先初始化mp 把不同道路间距离整成很远的一个数}for(int i=1;i<=m;i++){int x,y,z;scanf("%d%d%d",&x,&y,&z);mp[x][y]=z;mp[y][x]=z;}int s,t;scanf("%d%d",&s,&t);for(int k=1;k<=n;k++)for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)mp[i][j]=min(mp[i][j],mp[i][k]+mp[k][j]);if(mp[s][t]==1e6) cout<<"-1"<<endl;else cout<<mp[s][t]<<endl;}


动态规划(DP算法)学习心得

我觉得吧,这种题型有些共同点:都是求max和min的,而且每一步的选择都会影响结果,那么选择从底向上(逆推)即“递推的简化——数组存储”。当然要注意把边界值(初始值)先设好。

但我觉得难在要根据题目所给的“输入要求”设计,好吧好像也没有很难。。只要把题读懂了,把一些变量设好就还好。

  递归到动规的一般转化方法

    递归函数有n个参数,就定义一个n维的数组,数组的下标是递归函数参数的取值范围,数组元素的值是递归函数的返回值,这样就可以从边界值开始, 逐步填充数组,相当于计算递归函数值的逆过程。

    动规解题的一般思路

    1. 将原问题分解为子问题

  •     把原问题分解为若干个子问题,子问题和原问题形式相同或类似,只不过规模变小了。子问题都解决,原问题即解决(数字三角形例)。
  •     子问题的解一旦求出就会被保存,所以每个子问题只需求 解一次。

    2.确定状态

  •     在用动态规划解题时,我们往往将和子问题相关的各个变量的一组取值,称之为一个“状 态”。一个“状态”对应于一个或多个子问题, 所谓某个“状态”下的“值”,就是这个“状 态”所对应的子问题的解。
  •     所有“状态”的集合,构成问题的“状态空间”。“状态空间”的大小,与用动态规划解决问题的时间复杂度直接相关。 在数字三角形的例子里,一共有N×(N+1)/2个数字,所以这个问题的状态空间里一共就有N×(N+1)/2个状态。

    整个问题的时间复杂度是状态数目乘以计算每个状态所需时间。在数字三角形里每个“状态”只需要经过一次,且在每个状态上作计算所花的时间都是和N无关的常数。

    3.确定一些初始状态(边界状态)的值

    以“数字三角形”为例,初始状态就是底边数字,值就是底边数字值。

    4. 确定状态转移方程

     定义出什么是“状态”,以及在该“状态”下的“值”后,就要找出不同的状态之间如何迁移――即如何从一个或多个“值”已知的 “状态”,求出另一个“状态”的“值”(递推型)。状态的迁移可以用递推公式表示,此递推公式也可被称作“状态转移方程”。

    数字三角形的状态转移方程:

    
  

    能用动规解决的问题的特点

    1)问题具有最优子结构性质。如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质。

这一点强烈明显地体现在背包问题中。


    2) 无后效性。当前的若干个状态值一旦确定,则此后过程的演变就只和这若干个状态的值有关,和之前是采取哪种手段或经过哪条路径演变到当前的这若干个状态,没有关系。



10.4(第二天):

今天学的是背包dp问题。
大致了解了01背包和完全背包,其实比较方便的是可以照猫画虎,主要是写出状态转移方程。
比如01背包中:
for(int i=1;i<=n;i++)
 for(int j=V;j>=w[i];j--)
    dp[j]=max(dp[j],dp[j-w[i]]+v[i]);

实际应用看看例题吧。


01背包每种物品只能取1次,完全背包每种物品可取无限次,多重背包每种物品可取有限次。




一。饭卡问题

title: 饭卡 杭电oj 2546
tags: [动态规划]

题目链接

Problem Description

电子科大本部食堂的饭卡有一种很诡异的设计,即在购买之前判断余额。如果购买一个商品之前,卡上的剩余金额大于或等于5元,就一定可以购买成功(即使购买后卡上余额为负),否则无法购买(即使金额足够)。所以大家都希望尽量使卡上的余额最少。

某天,食堂中有n种菜出售,每种菜可购买一次。已知每种菜的价格以及卡上的余额,问最少可使卡上的余额为多少。

 

Input

多组数据。对于每组数据:

第一行为正整数n,表示菜的数量。n<=1000。

第二行包括n个正整数,表示每种菜的价格。价格不超过50。

第三行包括一个正整数m,表示卡上的余额。m<=1000。

n=0表示数据结束。

 

Output

对于每组输入,输出一行,包含一个整数,表示卡上可能的最小余额。

 

Sample Input

1505101 2 3 2 1 1 2 3 2 1500

 

Sample Output

-4532

分析:

本题就是01背包dp问题。不过要用sort排序,以把最贵的东西最后买(由题意,卡上的剩余金额大于或等于5元,就一定可以购买成功(即使购买后卡上余额为负))。

解题代码:

#include <bits/stdc++.h>using namespace std;int main(){int n,V,w[1005],dp[1005];while(cin>>n && n){memset(dp,0,sizeof(dp));for(int i=1;i<=n;i++) cin>>w[i];cin>>V;sort(w,w+1+n);if(V<5) cout<<V<<endl;else{for(int i=1;i<=n;i++){ for(int j=V-5;j>=w[i];j--){  dp[j]=max(dp[j],dp[j-w[i]]+w[i]); }}cout<<V-dp[V-5]-w[n]<<endl;}} return 0;}

注意:01背包若用一维数组解题,空间V的for循环中要逆序循环,因为dp[i]取决因素其实是(用二维数组书写)dp[i-1][j]和dp[i-1][j-w[i]]+w[i],而明显,dp[i-1][j-w[i]]在一维中表示为dp[j-w[i]],即:这次装东西必须要看上次装(上一个)东西时的最优决策。若 V正序,则会把在(i-1)循环时赋好的这个值给修改掉,不行!所以要逆序。


然而,完全背包的V的for循环要正序循环,原因是因为它装同一个东西可以装无限次你知道吗,所以这次装东西可以取决于上次装(同一)东西的最优决策!

然后,需要理解下面的图(初始化细节):


而怎么设无穷呢?




好,那接下来看看完全背包问题:

二.装硬币问题(杭电1114

题意:已知空罐重量、当前重量、每种硬币的重量和面值,要求根据给定的储钱罐重量求出储钱罐内至少含有多少钱。
思路:要求恰好装满的完全背包(求最小值)

Input
The input consists of T test cases. The number of them (T) is given on the first line of the input file. Each test case begins with a line containing two integers E and F. They indicate the weight of an empty pig and of the pig filled with coins. Both weights are given in grams. No pig will weigh more than 10 kg, that means 1 <= E <= F <= 10000. On the second line of each test case, there is an integer number N (1 <= N <= 500) that gives the number of various coins used in the given currency. Following this are exactly N lines, each specifying one coin type. These lines contain two integers each, Pand W (1 <= P <= 50000, 1 <= W <=10000). P is the value of the coin in monetary units, W is it's weight in grams. 

#include<bits/stdc++.h>using namespace std;const int INF = 0x3f3f3f3f;const int maxn=1e5;int main(){int t,e,f,n;   //t是共有几组待测case,e是空罐重量,f是满罐重量,n是硬币种类数。scanf("%d",&t);while(t--) {scanf("%d%d",&e,&f);scanf("%d",&n);int dp[maxn];dp[0]=0;for(int i=1;i<=maxn;i++){dp[i]=INF;}for(int i=1;i<=n;i++){int weight,value;     //不用整数组了,因为无限次用当前种类,与前无关。 scanf("%d%d",&weight,&value);for(int j=weight;j<=f-e ;j++)        //完全背包,j从weight到背包最大容量顺序循环  dp[j]=min(dp[j],dp[j-weight]+value);                   }if(dp[f-e]==INF) printf("This is impossible.\n");else printf("The minimum amount of money in the piggy-bank is 100.\n");}return 0;} 


【多重背包】:即每个种类的物品是有限个数!其实就是多加一层循环!

三。杭电oj2191

Input
输入数据首先包含一个正整数C,表示有C组测试用例,每组测试用例的第一行是两个整数n和m(1<=n<=100, 1<=m<=100),分别表示经费的金额和大米的种类,然后是m行数据,每行包含3个数p,h和c(1<=p<=20,1<=h<=200,1<=c<=20),分别表示每袋的价格、每袋的重量以及对应种类大米的袋数。
 
Output
对于每组测试数据,请输出能够购买大米的最多重量,你可以假设经费买不光所有的大米,并且经费你可以不用完。每个实例的输出占一行。
 
#include<bits/stdc++.h>using namespace std;int main(){int C,n,m,dp[105];int value[105],weight[105],bag[105];scanf("%d",&C);while(C--){memset(dp,0,sizeof(dp));scanf("%d%d",&n,&m);for(int i=1;i<=m;i++){cin>>value[i]>>weight[i]>>bag[i]; //分成3个数组, 各自存储不同的属性!!!!for(int k=1;k<=bag[i];k++) for(int j=n;j>=value[i];j--) //要倒序,因为我想用的是装(同种类)装上一个物品时的dp[j]和dp[j-value[i]],不便给它先改了    dp[j]=max(dp[j],dp[j-value[i]]+weight[i]); }cout<<dp[n]<<endl;} return 0;}



原创粉丝点击