动态规划是将大问题划分为小问题进行解决,从而一步步获取最优解的处理算法。
适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解。
经典背包问题,最长回文子串以及搜索算法中常见的编辑距离问题可以用动态规划解决
以上列举的问题除了利用动态规划算法解决外,也可以使用DFS算法解决,DP算法主要在于难以构造设计,DFS相对更易理解。
P1048 [NOIP2005 普及组] 采药
本来写是超时了的。。。(如下图)
#include<iostream>
using namespace std;
int t,m;//采药时间,草药数目
int res[1005][1005];
struct caoyao{
int time;//采药时间
int value;//价值
};caoyao a[1005];
int dfs(int num,int time){
int ans=0;
if(num==0)ans=0;
else if(a[num].time>time)ans=dfs(num-1,time);
else ans=max(dfs(num-1,time),dfs(num-1,time-a[num].time)+a[num].value);
res[num][time]=ans;
return ans;
}
int main(){
cin>>t>>m;
for(int i=1;i<=m;i++){
cin>>a[i].time>>a[i].value;
}
cout<<dfs(m,t); //总物品数,总时长
return 0;
}
memset函数 :在一段内存块中填充某个给定的值
用法:void *memset(void *s, int ch, size_t n);
函数解释:将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s 。
正确写法:
有两种写法,一种写在主函数内,一种新写一个函数:
1.
#include<iostream>
using namespace std;
int main(){
int a[10005],b[10005];
int t,m;
cin>>t>>m;
int dp[10005][10005];
for(int i=0;i<m;i++)cin>>a[i]>>b[i];
for(int i=0;i<m;i++){
for(int j=0;j<=t;j++){
if(j<a[i])dp[i+1][j]=dp[i][j];
else dp[i+1][j]=max(dp[i][j],dp[i][j-a[i]]+b[i]);
}
}
cout<<dp[m][t];
return 0;
}
2.
#include<iostream>
#include<string.h>
using namespace std;
int t,m;//采药时间,草药数目
int res[1005][1005];
struct caoyao{
int time;//采药时间
int value;//价值
};caoyao a[105];
int dfs(int num,int time){
if(res[num][time]!=-1){
return res[num][time];
}
int ans=0;
if(num==0)ans=0;
else if(a[num].time>time)ans=dfs(num-1,time);
else ans=max(dfs(num-1,time),dfs(num-1,time-a[num].time)+a[num].value);
res[num][time]=ans;
return ans;
}
int main(){
cin>>t>>m;
for(int i=1;i<=m;i++){
cin>>a[i].time>>a[i].value;
}
memset(res,-1,sizeof(res));
cout<<dfs(m,t); //总物品数,总时长
return 0;
}
P1115 最大子段和
可以理解成,对于每一个i位置上的数,它对应的序列长度等于max(所有小于i的位置上的长度+1,这个位置上原来最大的长度),然后从小到大遍历,就可以求出每一层的序列长度了。由于最后一层不一定是最大的,所以引入ans在每层循环替换最大值。
#include<iostream>
using namespace std;
int n;
int a[5005];int vis[5005];int ans=0;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];vis[i]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
if(a[j]<a[i]){
vis[i]=max(vis[i],vis[j]+1);
}
}
ans=max(ans,vis[i]);
}
cout<<ans<<endl;
return 0;
}
P1115 最大子段和
直接遍历时间会超,所以找到每个点之间的关系,比较每个数的结果与它前一个数的结果加上这一个数,得出最大值。
#include<iostream>
using namespace std;
int n;
int a[200005];int vis[200005];int ans=-9999999;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];vis[i]=a[i];
}
for(int i=1;i<=n;i++){
vis[i]=max(vis[i],vis[i-1]+a[i]);
if(ans<vis[i])ans=vis[i];
}
cout<<ans;
return 0;
}
LCS
#include<iostream>
#include<algorithm>
using namespace std;
//子问题答案(第三维两个元素分别存长度和条件转移方程的类型(用来向前寻找所有的子序列中的字符))
int ans[3001][3001][2];
int main()
{
//s和t
string s, t;
cin >> s >> t;
//两个字符串的长度m和n
int m = s.size(), n = t.size();
//在s和t最开头插入一个任意字符,字符串中真正的内容从索引1开始,便于后面循环
s.insert(s.begin(), ' ');
t.insert(t.begin(), ' ');
//动态规划
//外层循环为状态i
//内存循环为状态j
for (int i = 1; i<= m; i++)
{
for (int j = 1; j <= n; j++)
{
//第一个状态方程的情况
if (s[i] == t[j])
{
ans[i][j][0] = ans[i - 1][j - 1][0] + 1;
//定义这种情况类型为3
ans[i][j][1] = 3;
}
//第二个状态方程的情况
else
{
//第二个状态方程前一个表达式大的情况
if (ans[i - 1][j][0] >= ans[i][j - 1][0])
{
ans[i][j][0] = ans[i - 1][j][0];
//定义这种情况类型为1
ans[i][j][1] = 1;
}
else
{
//第二个状态方程后一个表达式大的情况
ans[i][j][0] = ans[i][j - 1][0];
//定义这种情况类型为2
ans[i][j][1] = 2;
}
}
}
}
//最长公共子序列
string r;
//ans[m][n][0]即使最长子序列的长度,但是题目要求子序列的内容,需要向前循环得到所有子序列中的字符
//循环得到子序列,由于不再使用m和n的值,直接使用m和n代表上面的i和j
while (ans[m][n][1] != 0)
{
//情况3,说明子序列中包含s[m](或t[n]),加到子序列中
if (ans[m][n][1] == 3)
{
r += s[m];
//更新m和n
m--;
n--;
}
//情况2和情况1,子序列不包含当前位置的字符
else if (ans[m][n][1] == 2)
{
//更新n
n--;
}
else if (ans[m][n][1] == 1)
{
//更新m
m--;
}
}
//反向得到的字符需要反转
reverse(r.begin(), r.end());
cout << r << endl;
return 0;
}