线性dp-LIS题目4(A Twisty Movement)

题目链接

1 解题思路

1.1 没有翻转的情况

这道题我们先思考一下,如果没有翻转这个条件的情况。这时,我们可以拆分成两种情况,一种是最长不下降子序列只有1,另一种是最长不下降子序列有1也可以有2,如果当前的数字 a i ai ai是1就只能接在第一种情况后面;如果当前数字 a i ai ai是2,可以接在最长不下降子序列有1也可以有2的序列后面。

我们记最长不下降子序列只有1的子序列长度为 d p 1 i dp1i dp1i,最长不下降子序列有1也有2的子序列长度为 d p 2 i dp2i dp2i。初始化所有 d p 1 dp1 dp1和 d p 2 dp2 dp2的元素为1,只有自己时,长度为1。

得到状态转移方程: d p 1 i = d p 1 i − 1 + ( a i = = 1 ) dp1i=dp1i-1+(ai==1) dp1i=dp1i−1+(ai==1), d p 2 i = m a x ( d p 1 i , d p 2 i − 1 + ( a i = = 2 ) ) dp2i=max(dp1i,dp2i-1+(ai==2)) dp2i=max(dp1i,dp2i−1+(ai==2))

注意: 在这里 d p 2 dp2 dp2不是必须接2,可能比较容易错写成: d p 2 i = m a x ( d p 1 i , d p 2 i − 1 ) + ( a i = = 2 ) dp2i=max(dp1i,dp2i-1)+(ai==2) dp2i=max(dp1i,dp2i−1)+(ai==2)。这里蕴含了贪心的思想。如果dp1的纯1序列长度比dp2长,后面再来 1 或者 2 都能合法接上,选择自由度最高。如果这时还要硬选2,按照不下降规则,末尾是 2 之后再也不能接 1,一旦后面出现 1,这条路线直接 "走死",灵活性极低。所以, d p 2 i dp2i dp2i的可以允许有2,但也可以不选2。最后,输出答案 d p 2 n dp2n dp2n

1.2 加入翻转的情况

如果加入翻转,我们再定义一个 d p 3 dp3 dp3, d p 3 dp3 dp3的更新在更新完 d p 2 dp2 dp2之后。一般来说,遇到 a i = 1 ai=1 ai=1的时候会反转,故 d p 3 i = m a x ( d p 2 i , d p 3 i − 1 + ( a i = = 1 ) ) dp3i=max(dp2i,dp3i-1+(ai==1)) dp3i=max(dp2i,dp3i−1+(ai==1)),同样也是贪心的思想,如果 d p 2 dp2 dp2长度更长,就不浪费这次翻转机会,后续元素要反转可以在 d p 2 dp2 dp2指向的子序列后面。如果 a i = 1 ai=1 ai=1且 d p 3 i − 1 + 1 dp3i-1+1 dp3i−1+1更大,这样意味着翻转区间右端点延长到 i i i这样得到的不下降子序列更长,那选择翻转。翻转区间左端点在有1也有2的序列里面,一般是第一个在2之后出现的1所在的下标。所以 d p 3 dp3 dp3的主要作用是看右区间是不是要延展到 i i i。

翻转之后,后面的元素可以接在最长不下降序列后面,比如翻转后如果当前元素 a i = 2 ai=2 ai=2,就可以接上去。如果 a i = 1 ai=1 ai=1,如果最长不下降序列全是1,那 d p 1 dp1 dp1那里已经累计过了,会继承到 d p 3 dp3 dp3;如果最长不下降子序列有1也有2,那么翻转后的最长不下降子序列最后一个是2,接不上。故我们再开一个 d p 4 dp4 dp4, d p i = m a x ( d p 3 i , d p 4 i − 1 + ( a i = = 2 ) ) dpi=max(dp3i,dp4i-1+(ai==2)) dpi=max(dp3i,dp4i−1+(ai==2))。最后答案输出 d p 4 n dp4n dp4n

更新的核心代码

bash 复制代码
for(int i=1;i<=n;i++){
	dp1[i]=dp1[i-1]+(a[i]==1);
    dp2[i]=max(dp1[i],dp2[i-1]+(a[i]==2));
    dp3[i]=max(dp2[i],dp3[i-1]+(a[i]==1));
    dp4[i]=max(dp3[i],dp4[i-1]+(a[i]==2));
}
1.3 空间复杂度优化

由于 d p dp dp值的更新只有上一次有关,和前面的无关,我们实际上可以用一个 d p dp dp数组存储, d p 0 dp0 dp0, d p 1 dp1 dp1, d p 2 dp2 dp2, d p 3 dp3 dp3分别存原来 d p 1 i dp1i dp1i, d p 2 i dp2i dp2i, d p 3 i dp3i dp3i, d p 4 i dp4i dp4i的值。这样空间复杂度就大大优化了。

2 AC Code

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int n,x,dp[4];
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>x;
		dp[0]=dp[0]+(x==1);
		dp[1]=max(dp[0],dp[1]+(x==2));
		dp[2]=max(dp[1],dp[2]+(x==1));
		dp[3]=max(dp[2],dp[3]+(x==2));
	}
	cout<<dp[3];
  return 0;
}

总结: 这题很锻炼思维。

相关推荐
To_OC11 小时前
LC 207 课程表:刚学图论那会儿,我连这是拓扑排序都没看出来
javascript·算法·leetcode
To_OC11 小时前
LC 208 实现 Trie 前缀树:曾被名字劝退,写完发现是送分题
javascript·算法·leetcode
BadBadBad__AK13 小时前
线段树维护区间 k 次方和
c++·数学·算法·stl
_清歌1 天前
DSpark 深度解读:DeepSeek-V4 如何用「半自回归」把推理速度提升 85%
算法
统计实现局1 天前
SVD 的三步走:双对角化、Givens 收敛、排序
算法
躬行见万象1 天前
《VLA 系列》UniLab 强化训练 | G1 机器人 |复现
算法
统计实现局1 天前
对称不定分解(Bunch-Kaufman):为什么 Cholesky 不够用
算法
统计实现局1 天前
dqrsl 拆解:拿着 QR 结果能算出哪 5 种东西
算法
统计实现局1 天前
为什么 Cholesky 求逆比 Gauss-Jordan 快一倍——行列式溢出防护详
算法