蓝桥杯算法入门_21 (dp入门 + 记忆化搜索)

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;
}

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值