动态规划:加速计算,化指为积
动态规划题目特点:
1:计数
- 有多少种方式到达终点
- 多少种方法选出k个数使得其值为s
2:求最大值最小值
- 最长上升子序列
3:存在性问题·:
- 取石子游戏,先手是否必胜
- 能否取出k个数值为s
动态规划(n*m)组成部分:
一:确定状态:
- 最后一步(最优策略中使用的最后一步)
- 化解子问题
二:转移方程:
- f()=min(max){ }
三:初始条件和边界情况
- 如数组下标不能为负
- 起始的状态
四:计算顺序:
- 从大到小(或者从小到大)
T1:采药
题目:
# [NOIP2005 普及组] 采药
## 题目描述
辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
如果你是辰辰,你能完成这个任务吗?
## 输入格式
第一行有 $2$ 个整数 $T$($1 \le T \le 1000$)和 $M$($1 \le M \le 100$),用一个空格隔开,$T$ 代表总共能够用来采药的时间,$M$ 代表山洞里的草药的数目。
接下来的 $M$ 行每行包括两个在 $1$ 到 $100$ 之间(包括 $1$ 和 $100$)的整数,分别表示采摘某株草药的时间和这株草药的价值。
## 输出格式
输出在规定的时间内可以采到的草药的最大总价值。
思路:
设置一个二维数组dp【i】【j】,其值代表当前状态下选取得到的最大价值,第一维度代表第i个物品,第二个维度代表当前状态下背包容量j。对于第i个物品i如果不挑选(或者背包容量不支持放下第i个物品)那么dp【i】【j】=dp【i-1】【j】。如果当前的背包容量支持放下第i个物品,那么就会产生一个状态转移:dp【i】【j】=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
#include<bits/stdc++.h>
using namespace std;
int T,M,v[1005],w[1005];//v[]代表价值,w代表重量
int dp[1005][10005];
int main()
{
cin>>T>>M;
for(int i=1;i<=M;i++){
cin>>t[i]>>v[i];
}
for(int i=1;i<=M;i++){
for(int j=1;j<=T;j++){
dp[i][j]=dp[i-1][j]//背包容量不够的情况
if(t[i]<=j) dp[i][j]=max(dp[i-1][j],dp[i-1][j-t[i]]+v[i]);//状态转移
}
}
cout<<dp[M][T];
return 0;
}
T2:最长上升子序列
题目:
# 最长上升子序列
## 题目描述
这是一个简单的动规板子题。
给出一个由 $n(n\le 5000)$ 个不超过 $10^6$ 的正整数组成的序列。请输出这个序列的**最长上升子序列**的长度。
最长上升子序列是指,从原序列中**按顺序**取出一些数字排在一起,这些数字是**逐渐增大**的。
## 输入格式
第一行,一个整数 $n$,表示序列长度。
第二行有 $n$ 个整数,表示这个序列。
## 输出格式
一个整数表示答案。
思路:
这道题感觉和线性代数求逆序数有点类似只不过这个是反正来(求正序数最大值?)
用一个f[i]数组代表第i个数字对应的最大上升序列的个数,然后对于每一个数字它的最小的f[i]=1,就是它本身,对于每一个数字的正序数最大值怎么求呢?去前面找比它还小的数:状态转移:f[i]=max(f[i],f[j]+1),然后答案就是f【i】中的最大值;
#include<bits/stdc++.h>
using namespace std;
int n,a[10005],f[10005],ans=0;
int main()
{
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];f[i]=1;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<i;j++){//寻找前面比他小的数
if(a[j]<a[i]){
f[i]=max(f[i],f[j]+1);
}
}
ans=max(f[i],ans);
//cout<<"f["<<i<<"]="<<f[i]<<endl;
}
cout<<ans<<endl;
return 0;
}
T3:最大子段和
题目:
# 最大子段和
## 题目描述
给出一个长度为 $n$ 的序列 $a$,选出其中连续且非空的一段使得这段和最大。
## 输入格式
第一行是一个整数,表示序列的长度 $n$。
第二行有 $n$ 个整数,第 $i$ 个整数表示序列的第 $i$ 个数字 $a_i$。
## 输出格式
输出一行一个整数表示答案。
思路:
用一个dp【】数组储存当前第i个数字对应的最大·子段和,状态转移方程:dp【i】=max(dp[i-1]+a[i],a[i])(如果dp[i-1]是一个1负数,那么选择这个数本身a【i】显然更优)
#include<bits/stdc++.h>
using namespace std;
int n,dp[200005],a[200005];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
int ans=a[1];
dp[1]=a[1];
for(int i=2;i<=n;i++){
dp[i]=max(dp[i-1]+a[i],a[i]);
ans=max(ans,dp[i]);//答案就是dp数组中的最大值
}
cout<<ans<<endl;
return 0;
}
T4:LCS
题目:
题目描述:
给定一个字符串 ss 和一个字符串 tt ,输出 ss 和 tt 的最长公共子序列。
输入格式:
两行,第一行输入 ss ,第二行输入 tt 。
输出格式:
输出 ss 和 tt 的最长公共子序列。如果有多种答案,输出任何一个都可以。
说明/提示:
数据保证 ss 和 tt 仅含英文小写字母,并且 ss 和 tt 的长度小于等于3000。
输入 #1复制
axyb abyxb
输出 #1复制
axb
思路:
设dp【i】【j】为a(1到i),b(1到j)的LCS长度,然后对应的边界dp【0】【j】=dp【i】【0】=0,状态转移方程为:dp【i】【j】=max(dp【i】【j-1】,dp【i-1】【j-1】,dp【i-1】【j-1】+1),然后用一个path【】数组记录下转移的路径就可以输出答案了。
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
const int N=3000,M=3000;
int n,m;
char a[N+5],b[M+5];
int dp[N+1][M+1],path[N+1][M+1]/*转移路径*/;
int main(){
int n,m;
cin>>a+1>>b+1;//让字符串下标从1开始
n=strlen(a+1);m=strlen(b+1);
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
dp[i][j]=dp[i-1][j];path[i][j]=1;//第1种
if(dp[i][j-1]>dp[i][j])dp[i][j]=dp[i][j-1],path[i][j]=2;//第2种
if(a[i]==b[j]&&dp[i-1][j-1]+1>dp[i][j])dp[i][j]=dp[i-1][j-1]+1,path[i][j]=3;//第3种
}
int nowi=n,nowj=m;
string ans;
while(nowi&&nowj)
if(pa[nowi][nowj]==1)nowi--;
else if(pa[nowi][nowj]==2)nowj--;
else ans+=a[nowi],nowi--,nowj--;
for(int i=ans.size()-1;i>=0;i--)cout<<ans[i];
return 0;
}