当一个问题可以分解为各种不同的子问题的时候,我们可以一步一步求子问题的解,最后再求原问题。
1,如何用动态规划做题:(思路)![](https://i-blog.csdnimg.cn/blog_migrate/e3b50357a5e8aa25b3833b1edff6a20c.jpeg)
做有关动态规划的题是分为两步:
(1)状态表示
什么意思呢?就是说首先需要判断该DP问题是一维还是二维,假设是二维,设为f[i][j],这时需要明确状态数组f表示的是什么。首先要看题目,题目问的啥就是什么,然后看该集合表示的是什么属性,比如是最大值,最小值,还是种数或者个数等。
(2)状态计算
首先需要看所有方案的集合该如何划分,这里有个标准就是说看最后一个不同点。
下面结合经典例题讲解如何利用动态规划思想做题。
经典例题
题意如上:
首先是分析,状态表示需要用二维,(当然也可以用一维去优化,具体思想和代码放到后面说),状态表示的是所有从前i件物品当中选,使得所有的物品的总体积不超过j的符合条件的所有方案。
属性就是表示所有选法的最大值。
状态计算就是先看集合如何划分,看最后一个不同点,假设当前选到了第i件物品,最后一个不同点就是第i件物品的选与不选,也就是说有两种方案,第i件物品的选与不选,
1,不选第i件物品
这时可以把问题简化成从所有前i-1个物品当中选,使得总体积不超过j的选法的所有集合。
2,选第i件物品
这时可以利用直接求不好求,采用从所有方案中减去第i件物品,也就是说从所有前i-1个物品当中选,使得总体积不超过j。对吗?由于是去掉了第i件物品,所以总体积应该是j-v[i],v[i]表示的是第i件物品的体积。此时求出最大值之后再把第i件物品加上就可以了。
最后求出所有子问题的最大值,再把所有子问题的最大值再取一个max就可以了。
代码如下:
#include<iostream>
using namespace std;
const int N=10010;
int n,m;
int w[N],v[N];
int f[N][N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>v[i]>>w[i];
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
f[i][j]=f[i-1][j];//第一种方案,不选第i件物品
if(j>=v[i])//如果状态体积大于第i件物品的体积,也就是能放下,因为在这里选第i件物品的方案有可能不存在,因为它可能放不下
{
f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);//求最大值
}
}
}
cout<<f[n][m]<<endl;//数组存的是选法的最大值
return 0;
}
那么,如何去优化呢?我们发现首先可以试着将两维优化成一维,用一个滚动数组不断刷新,代码里前面不变,后面的话 f[i][j]=f[i-1][j];改成f[j]=f[j],这是个恒等式,不用写,为什么呢?首先是在循环过程中,我们发现每一次右边的f[i-1]是属于上一次循环,也就是说第i-1次循环得到f[i-1]之后将f[i-1]的值赋值给f[i],而f[j]=f[j],易知右边的f[j]是上一次循环得到的,也就是第i-1次循环得到的f[j]在第i次循环里面赋值给了新的f[j],下面一部分的优化可以将i那一维删去之后得到
f [ j ]= max(f [ j ], f [ j - v [ i ]] + w[ i ] ),此时我们需要判断它是否和原式子等价。首先右边的f[j]肯定是上一次循环得到的f[j],由于j是从小到大枚举的,所以j-v[i]肯定是小于j的,因此此时的j-v[i]是第i层循环得到的,与原式子不符合,所以我们可以让j从大到小枚举,因为在j从大到小枚举的过程中,我们发现每次j-v[i]都没有被更新过,所以就可以进行优化。
代码如下:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;
int v[N],w[N];
int f[N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>v[i]>>w[i];
}
for(int i=1;i<=n;i++)
{
for(int j=m;j>=v[i];j--)
{
//f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
cout<<f[m]<<endl;
system("pause");
return 0;
}
这个题就比较简单了,首先分析:状态如何表示,考虑用二维,然后表示所有(假设下标从1开始)(1,1)这个点到(i,j)的集合,属性是最大值。接下来状态计算,考虑如何划分集合,就是找最后一个不同点,首先要想走到下标为i,j的点有两种方案,第一是从下标为i-1,j的点走到i,j。因此这种可以表示为f[i-1][j]+num[i][j],这里还要用到上面谈到的直接求不好求,把最后一个点先去掉,求出最大值之后再加上就可以了,第二是从下标为i,j-1的点走到i,j。这种思路同上。
代码如下:
#include<iostream>
using namespace std;
const int N=1010;
int f[N][N];
int num[N][N];
int t;
int main()
{
cin>>t;
while(t--)
{
int r,c;
cin>>r>>c;
for(int i=1;i<=r;i++)
{
for(int j=1;j<=c;j++)
{
cin>>num[i][j];
}
}
for(int i=1;i<=r;i++)
{
for(int j=1;j<=c;j++)
{
f[i][j]=max(f[i-1][j]+num[i][j],f[i][j-1]+num[i][j]);
}
}
cout<<f[r][c]<<endl;
}
system("pause");
return 0;
}
至于优化什么的还是留给读者朋友去探索吧。
题意如下:
首先需要把子序列跟子串这两个概念区别开,子串意思是是一段连续的字母或者字符,子序列,
不一定连续,只要顺序不变就可以。
首先看状态表示:这里的话用一维表示,集合表示所有以a[i]结尾的上升子序列的长度,属性表示最大值
然后,看状态计算。如何划分集合呢?这里由于是严格单调递增,所以需要保证前面的所有元素都要小于最后一个元素。划分的话是找最后一个不同点,显然最后一个不同点就是第i-1个元素,也就是倒数第二个元素。我们可以看倒数第二个元素是可以分为几类,首先看倒数第二个元素是否合法,也就是是否存在,当其不存在的时候,也就是只有a[i]这一个元素,所以就是1,然后就是a[1],a[2],...,a[i-1],怎么算呢?我们可以首先把最后一个元素去掉,也就是a[i],此时就是f[k],此时就是说还是直接求不好求,然后可以把最后一个元素去掉,先求其他的所有元素的最大值,再加上一就可以了。代码如下:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int n;
int a[N];
int f[N];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
int ans=0;
for(int i=1;i<=n;i++)
{
f[i]=1;
for(int j=1;j<i;j++)
{
if(a[i]>a[j])
{
f[i]=max(f[i],f[j]+1);
}
}
ans=max(ans,f[i]);//答案,每次更新答案即可
}
cout<<ans<<endl;
return 0;
}
今天就先写到这里,后续更新蓝桥杯真题解析。觉得有用的小伙伴点个关注(手动狗头)