动态规划
1.递推算法
例题 位数问题
【问题描述】
在所有的N位数中,有多少个数中有偶数个数字3?由于结果可能很大,你只需要输出这个答案对12345取余的值。
【输入格式】
读入一个数N
【输出格式】
输出有多少个数中有偶数个数字3。
【输入样例】
2
【输出样例】
73
【数据规模】
1<=N<=1000
【样例说明】
在所有的2位数字,包含0个3的数有72个,包含2个3的数有1个,共73个
理解
考虑这种题目,一般来说都是从第i-1位推导第i位,且当前位是取偶数还是取奇数的。
可以用f[i][0]表示前i位有偶数个3的方案数
f[i][1]表示前i位有奇数个3的方案数
则状态转移方程可以表示为:
f[i][0]=f[i-1][0]*9+f[i-1][1];
f[i][1]=f[i-1][0]+f[i-1][1]*9;
边界条件:f[1][1]=1; f[1][0]=8;
例题代码
#include <iostream>
using namespace std;
const int M=12345;//取模
const int N=1000+5; //最多1000位数
int main()
{
//f[i][0]表示前i位有偶数个3的方案数,f[i][1]表示前i位有奇数个3的方案数
int f[N][2],n;
f[1][0]=8;f[1][1]=1;//1位数的情况
cin>>n;
for(int i=2;i<=n;i++){
f[i][0]=(f[i-1][0]*9+f[i-1][1])%M;
f[i][1]=(f[i-1][1]*9+f[i-1][0])%M;
}
cout<<f[n][0]<<endl;
return 0;
}
2.基础dp
算法思想
如果各个子问题不是独立的,如果能够保存已经解决的子问题的答案,而在需要的时候再找出已求得的答案,这样就可以避免大量的重复计算。
基本思路是,用一个表记录所有已解决的子问题的答案,不管该问题以后是否被用到,只要它被计算过,就将其结果填入表中。
求解过程
基本步骤
动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值(最大值或最小值)的那个解。
动态规划法设计算法一般分成三个阶段:
(1)分段:将原问题分解为若干个相互重叠的子问题;
(2)分析:分析问题是否满足最优性原理,找出动态规划函数的递推式;
(3)求解:利用递推式自底向上计算,实现动态规划过程。
动态规划法利用问题的最优性原理,以自底向上的方式从子问题的最优解逐步构造出整个问题的最优解。
滚动数组
•处理dp[][]状态数组的时候,有个小技巧:把它变成一维的dp[],以节省空间。
•观察二维表dp[][],可以发现,每一行是从上面一行算出来的,只跟上面一行有关系,跟更前面的行没有关系。
•那么用新的一行覆盖原来的一行就好了。
3.求最长递增子序列
1、暴力法:枚举所有的子序列,判断是不是递增的,如果是递增的求最大值。
2、采用LCS的方法:
1)原序列A排序得到B
2) 求A和B的LCS
3、直接DP
4、借助二分查找,优化为O(nlogn)
dp解法
先确定动态规划的状态,这个问题可以用序列某一项作为结尾来作为一个状态。
用dp[i]表示一定以第i 项为结尾的最长上升子序列。
用a[i] 表示第i 项的值
如果有j < i且a[j] < a[i],那么把第i 项接在第j 项后面构成的子序列长度为:dp[i] = dp[j] + 1。
•要使dp[i] 为以i 结尾的最长上升子序列,需要枚举所有满足条件的j。所以转移方程是:
3.递推与记忆化搜索
•前面DP的状态转移,都是用递推的方法。
•有另一种方法,逻辑上的理解更加直接,这就是用“递归+记忆化搜索”来实现DP。
记忆化搜索思想
用递归实现DP时,在递归程序中记录计算过的状态,并在后续的计算中跳过已经算过的重复的状态,从而大大减少递归的计算次数,这就是“记忆化搜索”的思路。
•递归时,有大量重复计算,其实能避免。
•观察第3层的中间数“1”,从第2层的“3”往下走会经过“1”,计算一次从“1”出发的递归;从第2层的“8”往下走会也经过“1”,又重新计算了从“1”出发的递归。所以,只要避免这些重复计算,就能优化。
7(30)
3(23) 8(21)
8(20) 1(13) 0(10)
2(7) 7(12) 4(10) 4(10)
4(4) 5(5) 2(2) 6(6) 5(5)
4.背包问题
1.0/1背包
2.完全背包
3.多重背包
4.混合背包
5.分组背包
6.依赖背包
我认为后面的背包问题都是由0/1背包问题衍生出来,所以把0/1背包问题思想理解透彻才是最根本的
0/1背包
一、定义状态:
a[i][j]:表示容量为j的背包选择前i件物品的最大价值和
二、状态转移方程
1、w[i] > j ,第i件物品太重了,容量为j的背包装不下
a[i][j] = a[i -1][j]
2、w[i] <= j
a[i][j] = max(a[i -1][j],a[i -1][j -w[i]] + v[i]);
理解
在0/1背包问题中,物品i或者被装入背包,或者不被
装入背包,设xi
表示物品i装入背包的情况,
则当xi=0时,表示物品i没有被装入背包,
xi=1时,表示物品i被装入背包。
根据问题的要求,有如下约束条件和目标函数:
按这样的规律一行行填表,直到结束。现在回头考虑,装了哪些物品。
看最后一列,15>14,说明装了物品5,否则价值不会变化。
#include<bits/stdc++.h>
intn,c;//n件物品,背包容量为c
intw[N],v[N];//重量是w,价值是V
cin>> n >> c;//读入物品数量n和背部的容量c
for(inti= 1;i <= n;i++)//读入每件物品的重量和价值
cin>> w[i] >> v[i];
//用dp方程建表,行表示物品[1,n],列表示背包容量[1,c]
//dp[i]表示容量为i的背包选择物品的最大价值和
intdp[N] = {
0};//初始化表为0
//顺序遍历n件物品
for(inti= 1;i <= n;i++){
for(intj = c; j >= w[i]; j--){
//背包容量[1,c],从右到左填表
//不选当前物品和选当前物品(背包容量变为j-w[i])两种方案求最大值
dp[j] = max(dp[j],dp[j -w[i]] + v[i]);
}
}
训练例题
数塔问题
题目描述
设有一个三角形的数塔,顶点为根结点,每个结点有一个整数值。从顶点出发,可以向左走或向右走,如图所示
若要求从根结点开始,请找出一条路径,使路径之和最大,只要输出路径的和。
输入
第一行为n(n<10),表示数塔的层数
从第2行至n+1行,每行有若干个数据,表示数塔中的数值
输出
输出路径和最大的路径值。
样例输入
5
13
11 8
12 7 26
6 14 15 8
12 7 13 24 11
样例输出
86
#include<bits/stdc++.h>
using namespace std;
int main(