关于DP动规
今天学了动规,简单记录一下自己理解了的:(要不俺就忘了)
首先,啥是DP???
动态规划,其实就是组合子问题的解来解决整个问题的解,由于每个子问题他只判断一次,所以不会重复计算,那就很牛啊!!!
专业术语(复制加粘贴):
1、 阶段:把所给求解问题的过程恰当地分成若干个相互联系的阶段,以便于按一 定的次序去求解,过程不同,阶段数就可能不同.描述阶段的变量称为阶段变量。
2、状态:某一阶段的出发位置成为状态,通常一个阶段有多个状态。状态通常可以用一个或一组数来描述,称为状态变量。
3、决策:一个阶段的状态给定以后,从该状态演变到下一阶段某个状态的一种选 择(行动)称为决策。描述决策变量称决策变量
4、策略和最优策略:所有阶段的决策有序组合构成一个策略。 最优效果的策略叫最优策略。
5. 状态转移方程:前一阶段的终点就是后一阶段的起点,对前一阶段的状态作出某种决策, 产生后一阶段的状态,这种关系描述了由k阶段到k+1阶段状态的演变规律,称 为状态转移方程。
其实也没什么好说的,直接上题吧!
T1:从n个数中取出k个数,使得他们的和最大
解一:sort排序
这就没啥可讲的了,你就建一个cmp让sort从大到小排序,选出前n大的数相加,就好啦,上代码!
//和最大问题(sort)
#include<iostream>
#include<algorithm>
using namespace std;
int a[1000000];//我不知道数据
int sum;
int cmp(int x,int y)
{
return x>y;
}
int main()
{
int n,k;//n是总个数,k是取出的数的个数
cin>>n>>k;
for(int i=0;i<n;i++)
{
cin>>a[i];
}
sort(a,a+n,cmp);
for(int i=0;i<k;i++)
{
sum+=a[i];
}
cout<<sum;
return 0;
}
解二:DP
用动规来解决,首先要想出状态转移方程(好像有点难很难)
我们首先定义一个二维数组f[i][j],前面的表示第几个数(i),后面表示已经选取了几个数(j)
那么我们分将其成两种情况:我选或不选当前的数,
当我选取当前数时,它们的和就是上次的结果加上这个数,上次的结果就是f[i-1][j-1](因为目前的数是i,所以上一个的考虑到的数就是i-1,又因我考虑了这个数,所以上个数就是j-1),当前的数是a[i]。那我们如果加上这个数,那么现在所选数之和就是 f[i-1][j-1]+a[i]
那么如果不选前面的数时,所得和其实就是选取上一个数时的和,即 f[i-1][j],
所以我们就可以得出其状态转移方程:f[i][j]=max( f[i-1][j-1]+a[i],f[i-1][j])
其他就好说了,具体如下
1 #include<iostream>
2 #define lxl long long
4 #define db double
5 using namespace std;
6 int f[10005][10005];
8 int a[N];
9 int n,k;
11 int main(){
12 cin>>n>>k;
13 for(int i=1;i<=n;i++)
14 cin>>a[i];
15 for(int i=1;i<=n;i++){
16 for(int j=1;j<=k;j++){
17 f[i][j]=max(f[i-1][j-1]+a[i],f[i-1][j]);
18 }
19 }
20 cout<<f[n][k];
21 return 0;
22 }
T2.关于线性DP
从n个数中找出最长上升子序列(可以不连续),输出其长度
解法一、dfs搜索(坑)
1 #include<iostream>
2 #include<cstdio>
3 #define ll long long
4 int n,a[1001],ans=0;
5 void dfs(int c,int now,int len)
6 {
7 if(c==n)
8 {
9 if(len>ans) ans=len;
10 return;
11 }
12 for(int i=c;i<=n;i++)
13 {
14 if(a[i]>now) dfs(i,a[i],len+1);
15 }
16 }
17 int main()
18 {
19 scanf("%d",&n);
20 for(int i=1;i<=n;i++)
21 {
22 scanf("%d",&a[i]);
23 }
24 dfs(1,-1,0);
25 printf("%d",ans);
26 return 0;
27 }
我就提一句,dfs中c代表的是当前状态,即目前处理到的数,now代表前一个采用了的数,ans代表当前状态的上升序列长度,最后比较每一个ans,找出最大的即可
但是很遗憾,人家太慢了。就过了两个点。。。
那我还是讲讲其他的方法吧
解法二、记忆化搜索
不算很难
直接上核心代码理解一下吧,毕竟人家不是咱的重点关注对象
f[1][1]=a[1][1];
for(int i=2;i<=n;i++){
for(int j=1;j<=i;j++)
f[i][j]=max(f[i-1][j-1],f[i-1][j])+a[i][j];
}
int ans=0;
for(int i=1;i<=n;i++)
ans=max(ans,f[n][i]);
cout<<ans;
正统解法,解法三:DP
用倒推正推都可以
①正推(其实就是 fk[x] =min{ fk-1[yi] +d [yi,x]}的一个载体)
1 #include<cstdio>
2 #define ll long long
3 using namespace std;
4 int n,a[1001],f[1001],p[1001];
5 int main()
6 {
7 scanf("%d",&n);
8 for(int i=1;i<=n;i++)
9 {
10 scanf("%d",&a[i]);
11 }
12 f[1]=1;
13 for(int i=2;i<=n;i++)
14 {
15 f[i]=0;
16 for(int j=1;j<i;j++)
17 {
18 if(a[j]<a[i]&&f[j]>f[i])
19 {
20 f[i]=f[j];
21 p[i]=j;
22 }
23 }
24 f[i]++;
25 }
26 int ans=f[1],k=1;
27 for(int i=2;i<=n;i++)
28 {
29 if(f[i]>ans)ans=f[k=i];
30 }
31 printf("%d",ans);
32 return 0;
33 }
啥意思呢,是这样的
首先我们要明确我们定义的三个数组分别代表着啥:
a数组代表我输入的数组,f[i]数组代表i对应的最长子上升子序列长度,p数组用于表示f[i]的最优值j的位置上,就是它的上一个既满足在它前面,又比他小的数,然后我们可以举个例子:
比如 : 1 4 3
首先 i 等于2,也就是从第2个数 4 开始,我们让f[1]等于1(等会你就知道为啥了),再用一个 j ,他的范围是 [1,i),也就是找 i前面的数,那这里由于j<i,所以 j 就只能等于1,那a[j]就等于1,我们判断一下,如果a[j]<a[i]而且f[j]>f[i],那我们就让i的长度变成j的长度,并让p[i]=j,结束后 i 变成3,j从0到1开始遍历,是0的时候,a[j]代表1,a[i]等于3,由于满足a[j]<a[i]而且f[j](它就是 f[1]==1)>f[i](==0),咱就进行一下操作:让f[i]=f[j]=1,i的前驱(q[i])=j=1;再轮到j=2,由于不满足a[j]<a[i],无法进行以下操作,所以不管他,那么现在我就又有了a[3]的前驱,重复以上操作,我们就得到了最优解,和他们所对应的前驱,自然就可以得到最长上升子序列啦!!!
为啥要这样??
原因是我们要找的是最长上升子序列,所以前面的数a[j]必须比后面的数a[i]大(a[j]<a[i]),而且 j 对应的序列长度应该大于 i 对应的,原因是我们想让最长的变成i所对应的长度,所以只有在 j 对应的长度比i对应的长度长的时候我才交换他们的长度,以保证 f[i]对应的是最大值,
p[i]=j 表示的是 i 的上一个即在它前面有比他小的数是 j(类似于它的前驱),我们把他记录下来,目的是便于最后输出。
最后让f[i]的长度加1,因为我们这些数每遇到一个就会加1,殊不知其实一开始只有一个数的时候长度为0,所以最后加1才是最终的长度。
倒序也一样:( fk[x ]= min{ fk+1[yi]+d [x,yi]})
1 //最长上升子序列(逆推)
2 #include<iostream>
3 #include<cmath>
4 using namespace std;
5 int a[1001];
6 int f[1001];//对应的最长上升子序列长度
7 int p[1001];
8 int ans;
9 //p数组用于记录 f[i]的最优值j的位置 ,就是它的上一个既满足在他前面有比他小的数,
10 int main()
11 {
12 int n;
13 cin>>n;
14 for(int i=1;i<=n;i++) cin>>a[i];
15 f[n]=1;p[n]=0;
16 for(int i=n-1;i>0;i--)
17 {
18 f[i]=0;
19 for(int j=i+1;j<=n;j++)
20 {
21 if(a[i]<=a[j]&&f[j]>f[i])
22 //两个条件:1.j对应的数比i大(保证上升)2.j的长度要大于i的长度,要不换他干啥
23 {
24 f[i]=f[j];//让i的长度变成j的长度
25 p[i]=j;//记录下来他的上一个即在它前面,又比他小的数,方便最后输出
26 }
27 }
28 f[i]++;
29 }
30 ans=f[1];
31 int k=1;
32 for(int i=2;i<=n;i++)
33 {
34 if(f[i]>ans) ans=f[k=i];//好高级
35 }
36 cout<<ans<<endl;//这是最长上升子序列长度
37 while(k>0)
38 {
39 cout<<a[k]<<" ";
40 k=p[k];
41 }
42 return 0;
43 }
道理跟正推就是一样的
说了这么多了,总结一下吧
其实不难发现,我们会把一个大问题分成无数个小问题,但是他有一个前提。就是子问题的局部最优会导致整个问题的全局最优,也就是说无论过去我是咋决策的,现在的最优决策都是由原来的子问题的最优决策构成的,那也就是说一旦我们解决了子问题的最优,那以后的较大规模的问题也就迎刃而解了,而且一个问题的最优解只会受上一个最优子问题的影响,其他的子问题非最优解对现在的问题没有任何影响!
这就是老师讲的最优化原理和无后效性。
那我就思考了一下我刚才提及到的最长上升子序列是怎么体现出这个特点的呢??(仅个人观点)
仔细想想,其实我一开始就记录了每一个数的前驱,即它的上一个既满足在它前面,又比他小的数。我虽然全都记录下来了,但是我们只将最后我们得出的最长上升子序列的每个元素和其对应的前驱输出,而不是把每个元素对应的前驱全都输出,也就是错误的(不满足条件的)我就不输出了,这就保证了我的没满足全局最优的数并不会影响我们最后满足条件的数列,这就满足了DP的基本特征(条件),即无后效性,所以才可以用DP来做这道题。
还有就是,做这道题的思路是这样的:
1、划分阶段(在最长上升子序列一题中就是指的 i 和 j 对应的每一个阶段吧)
2、确定状态和变量(在这一题中我们运用了三个数组,分别表示 a[]原始数列、f[i] :i对应的最长子序列长度、g[i]: f[i]的最优值j的位置 ,就是它的上一个既满足在他前面有比他小的数)
3、确定状态转移方程
4、找出边界条件(这里就是 i 到头就行)
T3、坐标DP
一般就是个二维数组,,,
比如这道摘花生的题,说是一片地,一些坐标上有花生,现在你在左上角,只能向右或向下移动,问你一路上能摘多少花生?
我们肯定要先建一个二位数组记录每个点花生的数量(没有那肯定就是0啊),那我现在就想写出状态转移方程,怎么思考?
反正我到一个点要不就是从上面下来要不就是从左边往右走,那我们就比较上面的摘花生个数和左面总共摘得的花生个数,取最大值在加上这个点摘得的花生数量就是到这个点后你所最多能摘的花生数,即
f[i,j]=max{ f[i-1 , j],f[ i , j-1] } + a[i,j],
这样再用一个递推就能实现了。那我们想,我们所得到的目前阶段的最大值,肯定是不会受非最优子阶段的影响。所以是可以用DP做的。
也整理了最近做过的几道题
T4、
题目讲的就是在一个数组中找到某几个连续的数使得它们的和最大,针对这样的题,当然使用DP啦
先输入他们,f[i]数组代表是 i 状态时的最好方案(最大的和),值得注意的是,f[1]的状态就是a[i],因为就他一个。,我们现在的主要任务就是寻找此问题的状态转移方程,
仔细想想,我们会发现,我们想要求得的第i个状态的最好方案无非就两种,要么加他,要么不加他,再取个max就好了。
得到:f[i]=max(f[i-1]+a[i],a[i])
下面就顺水推舟的出来了
1 #include<iostream>
2 #include<cmath>
3 using namespace std;
4 int a[1000001];
5 int f[1000001];
6 int maxn=-1;
7 int main()
8 {
9 int n;
10 cin>>n;
11 for(int i=1;i<=n;i++)
12 {
13 cin>>a[i];
14 }
15 f[1]=a[1];
16 for(int i=1;i<=n;i++)
17 {
18 f[i]=max(f[i-1]+a[i],a[i]);
19 maxn=maxn>f[i]? maxn:f[i];
20 }
21 cout<<maxn;
22 return 0;
23
24 }
T5、
求两个序列的最长公共子串的长度,这个子串要求在序列中是连续的
只要找到我们想要的状态转移方程就好,关键就是咋想呢??
我先遍历每一个数组元素,将其分为两种情况,其中一种就是两个元素不相同的,我直接continue进行下一个就好,对于两者相同的,其长度不就是两个元素各自的数组中其前一个数的长度再加1吗,那方程就自然而然的推出来了:f[i][j]=f[i-1][j-1]+1,然后我们只要比较每一个f[i][j],取他们的最大值就好了
代码如下
#include<iostream>
#include<cstdio>
using namespace std;
char a[1001],b[1001];
int f[1001][1001];
int ans;
int main()
{
int m,n;
cin>>m>>n;
for(int i=1;i<=m;i++)
{
cin>>a[i];
}
for(int i=1;i<=n;i++)
{
cin>>b[i];
}
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
if(a[i]==b[j])
{
f[i][j]=f[i-1][j-1]+1;
ans=max(ans,f[i][j]);
}
}
}
cout<<ans;
return 0;
}
还有就是关于数字金字塔啥的,其实都一样,找出它的状态转移方程就好了,比如金字塔那玩意,想要在金字塔的上面走一条路使得经过的数字之和最大,我们只要用过列出状态转移方程(直接看代码吧,不啰嗦了,就是这个数它上面的和左上的f的max再加上自己就好了),得到每个金字塔最低端的终点,再比较哪个终点的f最大就输出,然后就开开心心地A掉了
1 #include<iostream>
2 #include<cmath>
3 using namespace std;
4 int a[1001][1001];
5 int f[1001][1001];
6 int ans;
7 int main()
8 {
9 int n;
10 cin>>n;
11 for(int i=1;i<=n;i++)
12 {
13 for(int j=1;j<=i;j++)
14 {
15 cin>>a[i][j];
16 }
17 }
18 f[1][1]=a[1][1];
19 for(int i=2;i<=n;i++)
20 {
21 for(int j=1;j<=i;j++)
22 {
23 f[i][j]=max(f[i-1][j-1],f[i-1][j])+a[i][j];
24 }
25 }
26 for(int i=1;i<=n;i++)
27 {
28 ans=ans>f[n][i]? ans:f[n][i];
29 }
30 cout<<ans;
31 return 0;
32 }
。。。。。。。
其实还有很多很多很难很难的题,小生孤陋寡闻,才疏学浅,这里就不再体现,以后会查缺补漏,日臻完善的!!
我就记录到这了,再见!!
2022/3/17
关于DP动规的更多相关文章
- hdu 1114 dp动规 Piggy-Bank
Piggy-Bank Time Limit:1000MS Memory Limit:32768KB 64bit IO Format:%I64d & %I64u Submit S ...
- dp 动规 最佳加法表达式
最佳加法表达式 有一个由1..9组成的数字串.问如果将m个加号插入到这个数字串中,在各种可能形成的表达式中,值最小的那个表达式的值是多少 解题思路 假定数字串长度是n,添完加号后,表达式的最后一个加号 ...
- 经典DP动规 0-1背包问题 二维与一维
先上代码 b站讲解视频 灯神讲背包 #include <iostream> #include <cstring> #include <algorithm> usin ...
- NOIP2013 提高组day2 2 花匠 动规 找拐点 树状数组
花匠 描述 花匠栋栋种了一排花,每株花都有自己的高度.花儿越长越大,也越来越挤.栋栋决定把这排中的一部分花移走,将剩下的留在原地,使得剩下的花能有空间长大,同时,栋栋希望剩下的花排列得比较别致. 具体 ...
- 【noip 2009】 乌龟棋 记忆化搜索&动规
题目背景 小明过生日的时候,爸爸送给他一副乌龟棋当作礼物. 题目描述 乌龟棋的棋盘是一行N个格子,每个格子上一个分数(非负整数).棋盘第1格是唯一的起点,第N格是终点,游戏要求玩家控制一个乌龟棋子从起 ...
- (动规 或 最短路)Help Jimmy(poj 1661)
http://poj.org/problem?id=1661 Description "Help Jimmy" 是在下图所示的场景上完成的游戏. 场景中包括多个长度和高度各不相同的 ...
- POJ-1958 Strange Towers of Hanoi(线性动规)
Strange Towers of Hanoi Time Limit: 1000MS Memory Limit: 30000K Total Submissions: 2677 Accepted: 17 ...
- POJ-1953 World Cup Noise(线性动规)
World Cup Noise Time Limit: 1000MS Memory Limit: 30000K Total Submissions: 16374 Accepted: 8097 Desc ...
- 2011 ACM-ICPC 成都赛区A题 Alice and Bob (博弈动规)
题目大意: 有K堆石子,每堆有Ki个.两人的操作能够是: 1 从某一堆拿走一个 假设该堆在此之后没有石子了.就消失 2 合并两个堆 求是否 ...
随机推荐
- Java基础复习(五)
1. 接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承具体类(concrete class)? 抽象类中是否可以有静态的main方法? 接口可以继承接口.抽象类可 ...
- 扩展NSDate类实现快捷使用 —— 昉
获取当前日期和时间: +(NSDate *)getCurrentDate{ NSDate *now = [NSDate date]; return now; } 将日期转换为字符串: +(NSStri ...
- 一款免费在线转pdf的工具 和 window免费镜像
PDF爱好者的在线工具 完全免费的PDF文件在线管理工具,其功能包括:合并PDF文件.拆分PDF文件.压缩PDF文件.Office文件转换为PDF文件.PDF文件转换为JPG图片.JPG图片转换为PD ...
- SQL——with as 临时表
一.WITH AS的含义 WITH AS短语,也叫做子查询部分(subquery factoring),可以让你做很多事情,定义一个SQL片断,该SQL片断会被整个SQL语句所用到.有的时候,是 ...
- Ceres 四重奏 之 入门简介
Ceres 翻译为谷神星,是太阳系中的一颗矮行星,于1801年被意大利神父 Piazzi 首次观测到,但随后 Piazzi 因为生病,跟丢了它的运行轨迹. 几个月后,德国数学家 Gauss,利用最小二 ...
- 使用代码绑定 DataGridView 控件用于程序界面显示表格
需求 软件界面需要使用表格,对数据进行显示.交互,这是一个非常通用的需求. 实现方法 DataGridView介绍 参考 https://docs.microsoft.com/en-us/dotnet ...
- jquery里的Ajax解析
现在对Jquery的Ajax进行详细的解析. 顺带,我会在后面把我整理的一整套CSS3,PHP,MYSQL的开发的笔记打包放到百度云,有需要可以直接去百度云下载,这样以后你们开发就可以直接翻笔记不用百 ...
- 关于git和SVN的介绍和区别
主要对git,svn进行一个简单的介绍. 顺带,我会在后面把我整理的一整套CSS3,PHP,MYSQL的开发的笔记打包放到百度云,有需要可以直接去百度云下载,这样以后你们开发就可以直接翻笔记不用百度搜 ...
- DHCPv4协议测试——信而泰网络测试仪实操
一.DHCP简介 1. DHCP原理 DHCPv4概述 上网最基本元素 · IP地址 · 子网掩码 · 缺省网关 · DNS服务器 DHCP概述-手工配置 为什么需要自动分配,手工配置不行吗? · 答 ...
- NFA转化为DFA
NFA(不确定的有穷自动机)转化为DFA(确定的有穷自动机) NFA转换DFA,通常是将带空串的NFA(即:ε-NFA)先转化为不带空串的NFA(即:NFA),然后再转化为DFA. 提示:ε是空串的意 ...