【蓝桥杯】备战系列之基础篇--动态规划思想及其经典例题

当一个问题可以分解为各种不同的子问题的时候,我们可以一步一步求子问题的解,最后再求原问题。

1,如何用动态规划做题:(思路)

做有关动态规划的题是分为两步:

(1)状态表示

什么意思呢?就是说首先需要判断该DP问题是一维还是二维,假设是二维,设为f[i][j],这时需要明确状态数组f表示的是什么。首先要看题目,题目问的啥就是什么,然后看该集合表示的是什么属性,比如是最大值,最小值,还是种数或者个数等。

(2)状态计算

首先需要看所有方案的集合该如何划分,这里有个标准就是说看最后一个不同点。

下面结合经典例题讲解如何利用动态规划思想做题。

经典例题

2. 01背包问题 - AcWing题库

 题意如上:

首先是分析,状态表示需要用二维,(当然也可以用一维去优化,具体思想和代码放到后面说),状态表示的是所有从前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;
}

1015. 摘花生 - AcWing题库

 这个题就比较简单了,首先分析:状态如何表示,考虑用二维,然后表示所有(假设下标从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;
}

至于优化什么的还是留给读者朋友去探索吧。

895. 最长上升子序列 - AcWing题库

题意如下:

 首先需要把子序列跟子串这两个概念区别开,子串意思是是一段连续的字母或者字符,子序列,

不一定连续,只要顺序不变就可以。

首先看状态表示:这里的话用一维表示,集合表示所有以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;
}

今天就先写到这里,后续更新蓝桥杯真题解析。觉得有用的小伙伴点个关注(手动狗头)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值