目录
一、概念
定义:动态规划是解决多阶段决策过程最优化问题的一种方法。
阶段:把问题分成几个相互联系的有顺序的几个环节,这些环节即称为阶段。
状态:某一阶段的出发位置称为状态。通常一个阶段包含若干状态。
决策:从某阶段的一个状态演变到下一个阶段某状态的选择。
策略:由开始到终点的全过程中,由每段决策组成的决策序列称为全过程策略,简称策略。
状态转移方程:前一阶段的终点就是后一阶段的起点,前一阶段的决策选择导出了后一阶段的状态,这种关系描述了由i阶段到i+1阶段的演变规律,称为状态转移方程。
形如:f[i] = f[i-1] + f[i-2], f[i][j] = max( f[i-1][j] , f[i-1][j-1]+a[i][j] )等等。
二、动态规划适用的基本条件:
1.具有相同子问题
(个别子情况可以少)如数字三角形(最左列的只能由正上方的得来,其余有上方和斜上方两种情况得来)
2.满足最优子结构
【如果确认是dp,那么状态要找对】(比如下图要求找除以4的余数要最大 的种类数,则不能写成由上一步最大余数的推下面的,因为可能有前面余数小的累加到后面变大(比如3个1累积),而大余数如3后面加个1就余0了)-->所以我们可以记录上一步余数为0,1,2,3的存在情况,这都对后面的余数大小产生影响。
3.满足无后效性
“过去的步骤只能通过当前状态影响未来的发展,当前的状态是历史的总结”。这条特征说明动态规划只适用于解决当前决策与过去状态无关的问题。状态,出现在策略任何一个位置,它的地位相同,都可实施同样的策略,这就是无后效性的内涵。如果当前问题的具体决策对解决其它未来的问题产生影响,就无法保证决策的最优性。
三、做动态规划的一般步骤
Third 考虑需不需优化
Forth 确定编程实现方式:1)递推 2)记忆化搜索【记忆化搜索可以不用理会遍历的顺序问题】
超基础【经典】题:
1.【NOIP2002]过河卒
注意边界(窝直接+1,弄堵墙保护数组),注意要开long long! qwq, 还有就是,一开始窝马的位置坐标也能少写真是醉了QWQ
#include<iostream>
using namespace std;
int n,m,x,y;
long long f[50][50];
int dx[]={0,2,1,-1,-2,-2,-1,1,2},dy[]={0,1,2,2,1,-1,-2,-2,-1};
int main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>m>>x>>y;
f[1][1]=1;
n++,m++,x++,y++;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(i==1&&j==1)continue;
f[i][j]=f[i][j-1]+f[i-1][j];
for(int k=0;k<9;k++)
if(i==x+dx[k]&&j==y+dy[k])f[i][j]=0;
}
}
cout<<f[n][m];
return 0;
}
2.【NOIP2008】传球游戏
f[i][j]代表第i轮传到第j个人的方案数,那么只能由上一轮j左边的人和 j右边的人传给ta, 注意因为是个环,当j==1时,左边为n,当j==n时,右边为1.
#include<iostream>
using namespace std;
long long f[40][40];
int n,m;
int main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>m;
f[0][1]=1;
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
int z=j-1,y=j+1;
if(j==1)z=n;
else if(j==n)y=1;
f[i][j]=f[i-1][z]+f[i-1][y];
}
}
cout<<f[m][1];
return 0;
}
3.最长不下降子序列
emmmm(懒得写了)
上图分别为递推和记忆化搜索的代码,在这题它的遍历顺序就可以直接按循环来,所以看上去递推的代码就已经很短了,但有些题,你先后处理的顺序就比较麻烦,这时候记忆化搜索的快乐就体现出来了,比如下一题...
4.滑雪
要是用递推的话,你必须得先确定最低点,由低到高的确定...(这时两重规规矩矩的for循环肯定是不行的,貌似要将所有点从小到大地排序,再一个个求....)而如果用记忆化搜索,就方便了很多。【记忆化搜索是一个非常“无脑化”的方式:算出来了就用,没算出来就调用这个函数去算.】
#include<iostream>
#include<algorithm>
using namespace std;
int ans,n,m,a[110][110],f[110][110],dx[]={-1,1,0,0},dy[]={0,0,-1,1};
int calc(int x,int y){
if(f[x][y])return f[x][y];//确定了就直接返回
f[x][y]=1;//首先自己肯定是一个长度
for(int i=0;i<4;i++){
int tx=x+dx[i],ty=y+dy[i];
if(a[tx][ty]<a[x][y]&&tx&&ty)//避免了进入f[0][y]/f[x][0]=1而对后面造成影响
f[x][y]=max(f[x][y],calc(tx,ty)+1);
}
return f[x][y];
}
int main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)cin>>a[i][j];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
ans=max(ans,calc(i,j));
cout<<ans;
return 0;
}
5.最大子串和
与上面第3个“最长不下降子序列”方法一样【这算是复习咩?】,f[i]代表的是前i个数中一定要取第i个数的最大和,(否则就无法往后推了)
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int M=1e6+6;
ll f[M],a[M],ans;
int main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int n,i;
cin>>n;
for(i=1;i<=n;i++)cin>>a[i];
for(i=1;i<=n;i++){
f[i]=max(a[i],f[i-1]+a[i]);
ans=max(f[i],ans);
}
cout<<ans;
return 0;
}
我天,总算整完一节课了,麻了QWQ
(确定不留个赞再走咩?)