dp入门 - 习题
普及5,代码风格不适应 kruskal 、prim (暂时保留,等待下回详解)
最小生成树 – 模板
最短路径: Floyd Dijkstra SPFA
…
普及6
dp入门篇
例题:
①硬币问题 : 不同面值 用最少数量凑目标值w ,硬币面值 1 5 10 20 50 100
先尽量用 100 ,再50 以此类推
(贪心 – 只考虑眼前,先选最大 ,不一定能行! 比如 1 5 11 选15 ,贪心11 + 4 * 1 多于 3 * 5 )
记凑出n需要用到的最少硬币数量为f(n),
那么 取了 11时 ,求f(4) = 5
取了 5时 ,求f(10) = 3
取了 1时 ,求f(14) = 5
最后选一个代价最小的作为答案
性质: f(n)只与 f(n - 1) 、f(n - 5)、f(n - 11) 有关
所以 cost = f(n) = min( min(f(n - 1),f(n - 5)) ,f(n - 11) ) + 1 //加第一次选取的
#include <iostream>
using namespace std;
#include<cstdio>
int test_01(){ //O(n) 把大问题最优解分解成了更小的子问题最优解 ,减少了计算
int f[105],i,n,cost;
scanf("%d",&n);
f[0] = 0; //0的时候不用选
for(int i = 1;i <= n;i++){ //从w = 1开始赋初始值 保证更新
f[i] = 233333333;
}
for(int i = 0;i <= n;i++){//w = 0 -- n 遍历输出
printf("f[%d]=%d\n",i,f[i]);
f[i + 1] = min(f[i + 1],f[i] + 1);
f[i + 5] = min(f[i + 5],f[i] + 1);
f[i + 11] = min(f[i + 11],f[i] + 1);
}
return 0;
}
DP使用条件:
能将大问题拆成几个小问题,且满足无后效性,最优子结构性质。
/*
例题:
> A * * > C
* * * *
S * * * > T
* > > *
> B * * > D
DAG最短路: 带权图,S -- >T最少路径花费
记S到p的最少花费为f(p) //中间任意点p
想要到T,要么经过C,要么经过D
f(p) = min( f(R),w[R->P]); //topo实现DP
*/
设计DP算法 ,DP三连:
我是谁(设计状态) - 我从哪里来 - 我要到哪里去 (转移方程)
主要靠做题练习总结
将来会学到DP的各种优化:数据结构优化、斜率优化
例题:
①观察下面的数字金字塔。
写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
看做每一次是三个数的小三角形判断(选左下和右下中最大的加入求和) – > 状态转移方程 :
f[i][j] = max (f[i + 1][j],f[i + 1][j + 1]) + a[i][j];
②最大子段和 --详见luogu – P1115
e.g.(举例) [2,-1,3,-5,3]的最大子段和是 2 - 1 + 3 = 4
要求一个O(n)的算法
设计状态: dp[i]表示 [1,i]位的最大子段和
dp[i] = max(dp[i - 1] + a[i], a[i]);
③最长上升子序列
如何设计状态: 记f(x)为以ax结尾的LIS长度,那么答案就是max(f(x))
推状态 考虑比x小的每一个p,如果ax > ap ,那么f(x) 可以取f§ + 1
即我们把ax接在ap后面,肯定能构造一个以ax为结尾的上升子序列,长度比以ap结尾的LIS大1
状态转移方程 : f(x) = max {f§} + 1 (p < x , ap < ax)
两层for O(n2) – 可以优化
记忆化搜索
记忆化搜索 P1464 //pop型:解决我从哪里来 ,递推push解决我到哪里去
引入:斐波那契数列优化 记忆计算结果,避免重复计算
输入:
10
输出:
calculate [10]
calculate [8]
calculate [6]
calculate [4]
calculate [3]
calculate [5]
calculate [7]
calculate [9]
int mem[10005]; //***memory记忆数组记录计算结果 反复使用
int fib(int n){
if(n == 1 || n == 2){
return 1;
}
if(mem[n]){
return mem[n];
}
printf("calculate [%d]\n",n);
return mem[n] = fib(n - 2) + fib(n - 1);
}
int test_02(){
int n;
scanf("%d",&n);
fib(n);
return 0;
}
计数类DP
①上楼梯 : n阶楼梯 ,上楼可以一步上1阶,也可以一步上2阶,问几种方法走完n阶楼梯,递归下去即可(DFS)
扩展:矩阵快速幂O(logn)求斐波那契数列第n项
②过河卒 (进阶版上楼梯) P1002
我们记走到(x,y)的路径数为f(x,y) ,答案即为 f(n,m)
不考虑那只吗,如何设计状态转移方程
卒只能向下或者向右!不能走回头路!
f(x,y) = f(x - 1,y) + f(x , y - 1)
斜方向的杨辉三角:
1 1 1 1 1
1 2 3 4
1 3 6 10
1 4 10 20
1
1
特判:钦定f(mark) = 0 , 其中mark为被骂控制的点和马本身的点
最终的方程是:
f(x,y) = 0 //(x,y)不能走
f(x,y) = f(x - 1,y) + f(x , y - 1) //其余情况
DP - 背包例题
1)装箱问题 P1049 (滚动数组) – 背包
二维 – 优化为一位
引用luogu:
这道题看似是搜索,但是可以用背包做。
题目要求求出最小的剩余空间,也就是要求出最大的可装重量
这样,我们可以将一个物体的重量当作它的价值,进而将题目转变为一个基本的01背包问题:
有一个箱子容量为V(正整数,0<=V<=20000),同时有n个物品(0<n<=30),每个物品有一个体积(正整数)和一个价值(等于体积)。
要求n个物品中,任取若干个装入箱内,使总价值最大。
对于每一个物体,都有两种状态:装 与不装
那么,对于任意重量m的最大价值 f (m) = max ( f ( m - w[i] ) + w[i], f (m) )(w为重量(即价值))
其中,f ( m - w[i] ) 指在装了物品i后,箱子的剩余容量能装的最大重量
f ( m - w[i] ) + w[i] 指在在装了物品i后,箱子能装的最大重量
#include<cstdio>
using namespace std;
int m,n; // m即箱子容量V
int f[20010];
int w[40];
int main(){
int i,j;
scanf("%d%d",&m,&n);
for(i=1;i<=n;i++){
scanf("%d",&w[i]);
}
for(i=1;i<=n;i++){
for(j=m;j>=w[i];j--){ // 注意:这里必须是从m到w[i],否则一个物体会被多次装入箱子,见例1
if(f[j]<f[j-w[i]]+w[i]){
f[j]=f[j-w[i]]+w[i];
}
}
}
printf("%d\n",m-f[m]);
}
2)乌龟棋 P1541 DP + 记忆化搜索
3)采药 P1048
int main() {
test_02();
return 0;
}