滚动数组线性dp
一、滚动数组
滚动数组是对动态规划中普通数组的空间复杂度进行优化的一个操作
1.一维数组:
dp[0] = 0
dp[1] = 1
dp[n] = dp[n-1] + dp[n-2]
进行优化后就是直接在原空间上进行覆盖式处理
dp[0] = 0
dp[0] = 1
在一维上的体现不是很大
2.二维数组:
dp[0][0] = 1
dp[i][j] = dp[i-1][j] + dp[i][j-1]
原代码:
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
if(i>=0){
dp[i][j] += dp[i-1][j];
}
if(j>0){
dp[i][j] += dp[i][j-1];
}
}
}
优化则可以实现对二维数组覆盖式处理成一维数组,实现对空间复杂度的优化处理
优化代码:
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
dp[j] += dp[j-1];
}
}
二、线性dp(动态规划)
1.动态规划算法基本思想
(1)动态规划(Dynamic Programming)算法的核心思想是:将大问题划分为小问题进行解决,从而一步步获取最优解的处理算法。
(2)动态规划算法与分治算法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
(3)与分治法不同的是,适合于用动态规划求解的问题,经分解得到的子问题往往不是互相独立的。(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)。
(4)动态规划可以通过填表的方式来逐步推进,得到最优解。
基本解题步骤
1.确定转移状态链
2.确定转移方式
3.确定状态转移方程
4.确定初始化和边界条件
例题一:
最长上升子序列
现有由n数组成的数列,求在这个数列中正向的最长数字列是多少
输入要求:
输入n代表数列包含的数,在之后输入数字
输出要求:
输出一个整数表示最长子序列长度
样例输入
5
1 5 3 4 6
输出样例
4
解题思路
1.确定转移状态链:
f[i] 表示第i个数前1到i个数可以组成最长子序列的长度
2.确定转移方式:
设立j遍历0到j,如果第i个数大于第j个数说明第i个数可以跟在第j个数后构成子序列,则将f[j]+1转移给f[i].
3.确定状态转移方程:
for(int i=1;i<=n;i++){
f[i]=1;
for(int j=1;j<=i-1;j++){
if(a[i]>a[j])
f[i]=max(f[i],f[j]+1);
}
}
4.确定初始化和边界条件:
f[i]=1 //每个数自身本身就是一个长度为1的子序列
代码实现
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N =1000000007;
int a[100010],f[100010];
int main()
{
int n;
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-1;j++){
if(a[i]>a[j])
f[i]=max(f[i],f[j]+1);
}
}
for(int i=1;i<=n;i++){
ans=max(ans,f[i]);
}
cout<<ans<<endl;
return 0;
}
例题二(例一进阶)
题目链接:
https://www.acwing.com/problem/content/484/
合唱队形
N 位同学站成一排,音乐老师要请其中的 (N−K) 位同学出列,使得剩下的 K 位同学排成合唱队形。
合唱队形是指这样的一种队形:设 K 位同学从左到右依次编号为 1,2…,K,他们的身高分别为 T1,T2,…,TK, 则他们的身高满足 T1<…Ti+1>…>TK(1≤i≤K)。
你的任务是,已知所有 N 位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
输入格式
输入的第一行是一个整数 N,表示同学的总数。
第二行有 N 个整数,用空格分隔,第 i 个整数 Ti 是第 i 位同学的身高(厘米)。
输出格式
输出包括一行,这一行只包含一个整数,就是最少需要几位同学出列。
数据范围 2≤N≤100,130≤Ti≤230
输入样例:
8
186 186 150 200 160 130 197 220
输出样例:
4
大概思路:
求最少出列,反向转换最长队形,则可以正向用个最长序反向用个最长序再进行遍历查出最长合唱队形
代码实现:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N =1000000007;
int a[100010],f[100010],g[100010];
int main()
{
int n;
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-1;j++){
if(a[i]>a[j])
f[i]=max(f[i],f[j]+1);
}
}
for(int i=n;i>=1;i--){
g[i]=1;
for(int j=n;j>=i+1;j--){
if(a[i]>a[j])
g[i]=max(g[i],g[j]+1);
}
}
for(int i=1;i<=n;i++){
ans=max(ans,f[i]+g[i]);
}
cout<<n-ans+1<<endl;
return 0;
}
例题三(2022蓝桥真题)
积木画
小明最近迷上了积木画,有这么两种类型的积木,分别为 I 型(大小为 2 个单位面积)和 L 型(大小为 3 个单位面积):
同时,小明有一块面积大小为 2×N 的画布,画布由 2×N 个 1×1 区域构成。
小明需要用以上两种积木将画布拼满,他想知道总共有多少种不同的方式?
积木可以任意旋转,且画布的方向固定。
输入格式
输入一个整数 N,表示画布大小。
输出格式
输出一个整数表示答案。
由于答案可能很大,所以输出其对 1000000007 取模后的值。
数据范围 1≤N≤107。
输入样例
3
输出样例
5
样例解释:
五种情况如下图所示,颜色只是为了标识不同的积木:
题目链接:
https://www.acwing.com/problem/content/description/4409/
代码实现:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll mod = 1000000007 ;
ll dp[500][3];
int main()
{
dp[1][0] = 1;
dp[1][1] = 2;
dp[1][2] = 1;
int n;
cin>>n;
for(int i=2;i<=n;i++)
{
dp[i&1][0] = (dp[i-1&1][0] + dp[i-1&1][2])%mod;
dp[i&1][1] = (dp[i-1&1][0]*2 + dp[i-1&1][1])%mod;
dp[i&1][2] = (dp[i-1&1][0] + dp[i-1&1][1])%mod;
}
cout<<dp[n&1][0]<<endl;
return 0;
}
&1(%2)
整个题的原始题解就是 i 和 i-1 但是本题空间有限制所以原始题解会存在空间复杂度过高的情况所以为了优化,就用到了滚动数组的思想,就是覆盖使用空间,观察题意本题的空间可以只使用两个列,所以 dp[500][3] 也可写作 dp[2][3] 然后 &1 和 %2 是等效的取模使空间得到优化