初识DP
写在前面的话:
其实在去年寒假奥赛集训的时候,就已经接触DP了,但自己是真得对那时的自己很无语,不会,想不通,记不住就不管了,也没想过要一定把它吃透--但该来的总还是要来的。
所以现在就来玩好玩的DP吧。
DP分类:
一、简单基础dp
这类dp主要是一些状态比较容易表示,转移方程比较好想,问题比较基本常见的。主要包括递推、背包、LIS(最长递增序列),LCS(最长公共子序列)
二、区间dp
三、树形dp
四、数位dp
五、状态压缩dp
好,柿子先从软的捏
先了解dp的基本思想吧。
在现实生活中,有些过程可以分成若干个相互联系的阶段,在它的每一阶段都要做出决策,从而使整个过程达到最好的活动效果。其中,各个阶段决策的选取既依赖当前面临的状态,又影响以后的发展,当个各阶段决策确定后,就构成了一个决策序列。
动态规划问题满足三大重要性质
- 最优子结构性质:如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。
- 子问题重叠性质:子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的效率。
- 无后效性:将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向性,又称为无后效性。
OK,现在就开始看第一个简单的模型吧--数字金字塔。
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
大体思想:
法1:顺推法
路径起点确定,中间点与终点相对不确定,定义f[x][y]为从(1,1)出发到达(x,y)的路径最大权值和。
因为要使从(x,y)到终点值最大,就要使(1,1)到(x,y)值最大,并且,到达(x,y)的路径就只有两条,一个左上,一个右上,当然,两边的点也不必担心,因为它的左上或右上的值为零,状态转移方程依然成立。
f[x][y]=max{f[i-1][y-1],f[i-1][y]}+a[x][y]。
最后,ans为f[n][1~n]最大的一个
法2:逆推法(新颖的脑回路)
由顶向下分析,自底向上计算。
f[x][y]=max{f[x+1][y+1]+f[x+1][y]}+a[x][y]。
for(int i=1;i<=n;i++)
f[n][i]=a[n][i];
and then
LIS(最长上升子序列)
#include<iostream>
#include<cstdio>
using namespace std;
int a[100],n,f[100],ans;//f[]表示从1~~n每个数所包含的最大上升子序列
int main() //a[]记录数字
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
for(int i=1;i<=n;i++)
{
f[i]=1;
for(int j=1;j<i;j++)
{
if(a[j]<a[i])
f[i]=max(f[i],f[j]+1);//f[j]+1表示若以a[j]为a[i]的下一个子序列时的最大子序列数
}//有可能a[j]过小以至于子序列数过短
}
for(int i=1;i<=n;i++)
{
if(f[i]>ans)
ans=f[i];//包含递推思想
}
cout<<ans;
return 0;
}
以上为0(n^2)
我自己的理解:
f[i]表示以a[i]结尾的序列的最长长度,其序列最短也还是有一个的(本身),so初始化为f[i]=1;
后面如果有比a[i]小的,例如我们先设为a[k]吧,那我们可以选择将以a[i]结尾的那一串数接在a[k]的后面,此时以a[k]结尾的序列长度就为f[i]+1;
当然,选择这个a[i]接在后面不一定是最长的,所以我们要将目前已知f[i]的值与选择a[i]接在前面从而得到的值比较,
即 f[i]=max(f[j]+1,f[i])。
依次遍历a[1]~a[n],然后sort一下就能得到lis了。
有0(nlongn)的写法,但是,好吧,我不会啊。。。
但不妨碍我粘个代码:
int n;
cin>>n;
for(int i=;i<=n;i++)
{
cin>>a[i];
f[i]=0x7fffffff;
//初始值要设为INF
/*原因很简单,每遇到一个新的元素时,就跟已经记录的f数组当前所记录的最长
上升子序列的末尾元素相比较:如果小于此元素,那么就不断向前找,直到找到
一个刚好比它大的元素,替换;反之如果大于,么填到末尾元素的下一个q,INF
就是为了方便向后替换啊!*/
}
f[]=a[];
int len=;//通过记录f数组的有效位数,求得个数
/*因为上文中所提到我们有可能要不断向前寻找,
所以可以采用二分查找的策略,这便是将时间复杂
度降成nlogn级别的关键因素。*/
for(int i=;i<=n;i++)
{
int l=,r=len,mid;
if(a[i]>f[len])f[++len]=a[i];
//如果刚好大于末尾,暂时向后顺次填充
else
{
while(l<r)
{
mid=(l+r)/;
if(f[mid]>a[i])r=mid;
//如果仍然小于之前所记录的最小末尾,那么不断
//向前寻找(因为是最长上升子序列,所以f数组必
//然满足单调)
else l=mid+;
}
f[l]=min(a[i],f[l]);//更新最小末尾
}
}
cout<<len;
LCS(最长公共子序列):
用 dp[i][j] 来表示第一个串的前 i位,第二个串的前j位的 LCS 的长度。
//T:最长公共子序列
#include <cstdio>
#include <algorithm> #define MAXN 2111 using namespace std; int n, m;
int a[MAXN], b[MAXN];
int f[MAXN][MAXN];
int main() {
scanf("%d%d", &n, &m);
for(int i = ; i <= n; i++) scanf("%d", &a[i]);
for(int i = ; i <= m; i++) scanf("%d", &b[i]); for(int i = ; i <= n; i++) {
for(int j = ; j <= m; j++) { f[i][j] = max(f[i - ][j], f[i][j - ]); if(a[i] == b[j]) f[i][j] = max(f[i][j], f[i - ][j - ] + ); else f[i][j] = max(f[i][j], f[i - ][j - ]); }
}
printf("%d\n", f[n][m]);
return ;
}
个人以为DP是贪心思想与递推思想的结合
OK,现在来看一些题目:
硬币问题
您有无限多的硬币,硬币的面值为 1,5,10,50,100,500.
给定一个数额 w,问您最少用多少枚硬币可以凑出 w.
依据生活经验,我们可以采用这种策略:
先尽量用500的,然后尽量用100的……以此类推。
e.g. 666 = 1*500+1*100+1*50+1*10+1*5+1*1,共用10枚硬币。
这就是贪心了。
我们每次使用一个硬币,总能最大程度地解决问题(把剩下要凑的数
额变小)。可是,贪心是一种只考虑眼前情况的策略。尽管这一套硬
币面值可以采用贪心策略,但是迟早要栽跟头的。
我们考虑一组新的硬币面值:1,5,11.
于是有了一个反例:如果我们要凑出15,贪心策
15 = 11+4*1,共用 5 枚硬币。
而最佳策略是:
15 = 3*5,共用3枚硬币。
贪心策略自此陷入困境:鼠目寸光。
在 w=15 时,贪心策略选择了面值 11 的硬币(因为这样可以尽可
能降低要凑的数额)。
在选择了面值为 11 的硬币之后,我们只好面对 w=4 的处境。
我们重新分析刚刚的情况:
w=15时,我们取了11,接下来面对w=4的情况。
w=15时,如果我们取5,接下来就面对w=10的情况。
我们记“凑出n需要用到的最少硬币数量”为 f(n).
那么,如果我们取了 11,则:
cost = f(4) + 1 = 4 + 1 = 5.
解释:我们用了一枚面值为 11 的硬币,所以加
接下来面对的是 w=4 的情况。f(4) 我告诉你等
相应地,如果我们选择取 5,则:
cost = f(10) + 1 = 2 + 1 = 3.
那么,w=15时,我们选哪枚硬币呢?
cost最低的那一个!
11: cost = f(4) + 1 = 4 + 1 = 5.
5: cost = f (10) + 1 = 2 + 1 = 3.
1: cost = f (14) + 1 = 4 + 1 = 5.
选择5,f( 15) = 3,即为答案!
f(n) = min {f(n− 1) , f(n − 5) ,f(n − 11) } + 1
for(int i = 1; i <=n ; i++{
cost=INF;
if(i-1>=0) cost=min(cost,f[i-1]+1);
if(i-5>=0) cost=min(cost,f[i-5]+1);
if(i-11>=0) cost=min(cost,f[i-11]+1);
f[i]=cost;
}
cout<<f[n];
依次将当前面值n与n-1 n-5 n-11面值的最小硬币数进行状态转移
这个算法的时间复杂度显然是O(n).为什么比暴力要快呢?
我们暴力枚举了“使用的硬币”,然而这属于冗余信息。
我们要的是答案,根本不关心这个答案是怎么凑出来的。
要求出f(15),只需要知道f (14) , f(10) , f(4)的值。
其他信息不需要。
要求出f(15),只需要知道f (14) , f(10) , f(4)的值,而
f (14) , f(10) , f(4)是如何算出来的,对之后的问题没有影响。
“未来与过去无关”,这就是无后效性。
初识DP的更多相关文章
- 「学习笔记」动态规划 I『初识DP』
写在前面 注意:此文章仅供参考,如发现有误请及时告知. 更新日期:2018/3/16,2018/12/03 动态规划介绍 动态规划,简称DP(Dynamic Programming) 简介1 简介2 ...
- 初识DP动态规划
一.多阶段决策过程的最优化问题 在现实生活中,有类活 动的过程,由于 它的特殊性,可将过程分成若干个互相阶段.在它的每一阶段都需要作出决策,从而使整个过程达到最好的活动效果.当阶段决策的选取不是任意确 ...
- hdu 1003 Max Sum (动态规划)
转载于acm之家http://www.acmerblog.com/hdu-1003-Max-Sum-1258.html Max Sum Time Limit: 2000/1000 MS (Java/O ...
- Algo: Dynamic programming
Copyright © 1900-2016, NORYES, All Rights Reserved. http://www.cnblogs.com/noryes/ 欢迎转载,请保留此版权声明. -- ...
- 状压DP初识~~炮兵阵地
炮兵阵地 Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 31718 Accepted: 12253 Descriptio ...
- hdu3507(初识斜率优化DP)
hdu3507 题意 给出 N 个数字,输出的时候可以选择连续的输出,每连续输出一串,它的费用是 这串数字和的平方加上一个常数 M. 分析 斜率优化dp,入门题. 参考 参考 得到 dp 方程后,发现 ...
- Android图片缓存之初识Glide
前言: 前面总结学习了图片的使用以及Lru算法,今天来学习一下比较优秀的图片缓存开源框架.技术本身就要不断的更迭,从最初的自己使用SoftReference实现自己的图片缓存,到后来做电商项目自己的实 ...
- 初识 Android
创建博客有一年的时间了,一直没把它用起来,颇感惭愧.近日突感有写博客的冲动,更可怕的是这种冲动似乎比我体内的洪荒之力更为凶猛.于是乎,这篇博客悄然诞生.废话不多说,进入正题--初识Android. 这 ...
- 初识DSP
初识DSP 1.TI DSP的选型主要考虑处理速度.功耗.程序存储器和数据存储器的容量.片内的资源,如定时器的数量.I/O口数量.中断数量.DMA通道数等.DSP的主要供应商有TI,ADI,Motor ...
随机推荐
- python 模块、包
#-----模块.包----- 模块: 一个.py 文件就称之为模块模块好处:1.提高代码可读性 2.编写代码不需要从零开始 python 模块: 1.python标准库 2.第三方模块 3.应用程序 ...
- Codeforces F. Maxim and Array(构造贪心)
题目描述: Maxim and Array time limit per test 2 seconds memory limit per test 256 megabytes input standa ...
- 201871010125 王玉江 《面向对象程序设计(Java)》第八周实验总结
项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 https://www.cnblogs.com/nwnu-daizh/p ...
- style="position: relative;left:-5px; top: -5px;"
<span class="input-sm" style="position: relative;left:-5px; top: -5px;">&l ...
- 并发、并行、同步、异步、全局解释锁GIL、同步锁Lock、死锁、递归锁、同步对象/条件、信号量、队列、生产者消费者、多进程模块、进程的调用、Process类、
并发:是指系统具有处理多个任务/动作的能力. 并行:是指系统具有同时处理多个任务/动作的能力. 并行是并发的子集. 同步:当进程执行到一个IO(等待外部数据)的时候. 异步:当进程执行到一个IO不等到 ...
- zz目标检测
deep learning分类 目标检测-HyperNet-论文笔记 06-06 基础DL模型-Deformable Convolutional Networks-论文笔记 06-05 基础DL模型- ...
- Excel-基本操作
一.EXCEL的数据类型 1.字符型 2.数值型 3.日期型数据和时间型数据 二.快捷键 ctrl+上下左右健 快速选择某区域 上下左右单元格 ctrl+shift+上下左右 快速选择某个取悦 三. ...
- 从网络服务生成Apex类
使用WSDL2Apex从网络服务生成Apex类 如果某个网络服务被定义在WSDL文件中,而Salesforce必须使用SOAP和网络服务进行通信,则这种情况在某些时候会为开发者带来很多麻烦.为了简化S ...
- [LeetCode] 727. Minimum Window Subsequence 最小窗口序列
Given strings S and T, find the minimum (contiguous) substring W of S, so that T is a subsequence of ...
- KMP 串的模式匹配 (25分)
给定两个由英文字母组成的字符串 String 和 Pattern,要求找到 Pattern 在 String 中第一次出现的位置,并将此位置后的 String 的子串输出.如果找不到,则输出“Not ...