洛谷P2008做题总结

题目链接

1 问题分析

适合用动态规划算法解决的问题需要满足:拥有最优子结构并且问题无后效性这两个条件。接下来,笔者将分析本题是否满足上述两个条件:

1.1 最优子结构

对于第一个数,其最长不下降子序列是1(它自己)。求第i个数(i>1)的最长不下降子序列,我们可以先找满足不大于第i个数的数,之后在这些满足条件的数里面找到最长的序列的长度,加1(要把第i个数也加进去),就可以得到第i个数的最长不下降子序列的长度。这样看,第i(i>1)个数的最长不下降子序列可以从前i-1个数的最长不下降子序列得到答案,而且是第1个数也是有答案的,满足最优子结构。

1.2 无后效性

通过1.1的分析,我们知道第一个数的最长不下降子序列是1;从第二个数起,其最长不下降子序列都源于前面i-1个数的最长不下降子序列,故无后效性,可以使用动态规划求解。

2 解题思路

我们可以从前往后依次确定每个数的最长不下降子序列长度,并求和。

2.1 存储结构

为此我们可以开三个数组:a,f,v。a数组用来存储每个数的值;f数组存储每个数的最长不下降子序列的长度;v数组存储最长不下降子序列中所有数的和。

2.2 判断条件及执行过程

在这里有一点需要注意的是,题目说了“如有多个最长不下降子序列,那么取编号字典序最小的”,所以,1.在遍历前面的数的顺序时应该从前往后遍历;2.在判断条件上,我们除了要判断前面的数小于等于当前的数之外,还有判断前面的数的最长不下降子序列长度+1是否大于当前数原本的最长不下降子序列长度。如果是,更新当前数最长不下降子序列的长度与所有数的和,这个在f和v中更新相应的元素,否则就在看后一个数的情况。用这两个条件可以找到最长的不降子序列并且字典序最小。

2.3 f和v数组的初始化

抛开题目测试数据的样例,我们看一下,如果n=5,给的是5,4,3,2,1的话,最后应该输出5,4,3,2,1。若2.2的判断语句用单分支结构实现,那么每一次执行循环体不满足条件,所以f和v中相应的元素都没有变化,等于初始值。根据数据特点,我们可以把f中的元素初始化为1;将a中的元素初始化时赋给v数组。

3 AC代码

#include<bits/stdc++.h>
using namespace std;
int f[10001],a[10001];
int v[10001];
int n;
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		f[i]=1;
		v[i]=a[i];
	}
	for(int i=2;i<=n;i++){
		for(int j=1;j<=i-1;j++){
			if(a[j]<=a[i]&&f[i]<f[j]+1){
				f[i] = f[j]+1;
				v[i] = v[j]+a[i];			
			}
		}
	}
	for(int i=1;i<=n;i++)
		cout<<v[i]<<" ";
	return 0;
} 
<think>好的,我现在需要解决用户提到的P1970题目的解法或思路。首先,我得先回忆一下这道题的具体内容。P1970是一道关于调整花坛中花的顺序以形成波动序列的问题。题目要求通过调整花的高度,使得相邻的花严格递增或递减,形成一个波浪形排列,最终求出最长的可行序列长度。 用户可能已经尝试过一些方法但遇到了困难,比如提到的类差分法解题P3998时的TLE问题,所以需要更高效的思路。我需要先明确问题的关键点:如何高效地找到最长波动子序列,而不需要实际调整花的顺序。 首先想到的是动态规划(DP)方法,因为这类最长子序列问题通常可以用DP来解决。对于每个位置i,我们需要记录两种状态:一种是以i结尾且最后一步是上升的,另一种是以i结尾且最后一步是下降的。这样可以分别用两个数组up和down来存储。 接下来,我需要考虑状态转移方程。假设当前处理到第i朵花,高度为h[i]。对于up[i],如果h[i] > h[j],那么可以从down[j]转移过来,即up[i] = down[j] + 1。同样,down[i]则是当h[i] < h[j]时,从up[j]转移过来。这样,每次遍历前面的所有j,取最大值,时间复杂度是O(n²),对于n较大的情况可能不够高效。 不过,题目中的n范围可能允许O(n²)的解法,或者有没有更优的方法?比如贪心策略。例如,维护当前是上升还是下降的状态,每次尽可能延长序列。这种方法的时间复杂度是O(n),但需要正确判断何时切换状态。 例如,初始化时,第一个元素作为起点。然后从第二个元素开始,如果当前元素比前一个大,且当前处于下降状态,则切换为上升,长度加一;反之亦然。这种方法是否能覆盖所有情况?可能需要考虑多个波动的情况,比如连续的上升或下降是否需要调整。 不过,贪心方法可能无法处理所有情况,例如中间有多个小的波动。比如序列是1,3,2,4,1,这时候可能需要选择不同的转折点。因此,动态规划可能更可靠,尽管时间复杂度较高,但正确性有保障。 另外,用户提到的类差分法可能是指类似差分数组的处理,但不确定具体如何应用到这题。或许可以结合差分思想,记录变化点,从而减少状态数量。 现在需要确认题目的数据规模。比如,如果n是1e5,那么O(n²)的DP显然不行,需要更优的方法。但如果是较小的n,比如1e3,那么O(n²)是可行的。假设题目中的n较大,可能需要优化。 查阅P1970的原题,题目中的数据范围是n ≤ 1e5,所以必须使用O(n)或O(n log n)的算法。这时候动态规划显然不行,必须用贪心方法。 那如何用贪心策略处理这个问题?参考类似最长摆动子序列的问题,比如LeetCode 376. Wiggle Subsequence,其中最优解法是O(n)的贪心。 该问题的解法思路是:遍历数组,记录当前是上升还是下降的状态。当遇到符合下一个状态的变化时,延长序列长度,并切换状态。例如,如果之前是上升,那么需要找到下一个下降的点,反之亦然。这种方法可以在一次遍历中完成,时间复杂度为O(n)。 具体来说,维护两个变量:up和down,分别表示到当前位置为止,以上升或下降结尾的最长摆动子序列的长度。初始化时,up = down = 1。然后从第二个元素开始,比较当前元素与前一个元素的大小: - 如果当前元素 > 前一个元素,则up = down + 1; - 如果当前元素 < 前一个元素,则down = up + 1; - 如果相等,则不变化。 这样,最终结果就是max(up, down)。这种方法不需要实际构建序列,只记录状态的变化,因此时间复杂度是O(n),空间O(1)。 但需要注意,这种方法适用于严格摆动的情况,即相邻元素必须严格递增或递减。而题目中P1970的要求正是严格波动,所以适用。 因此,正确的解法应该是这种贪心策略,遍历一次数组,维护up和down两个变量,每次根据当前与前一个元素的关系更新它们。最终得到的最大值即为答案。 现在需要验证这个思路是否正确。例如,测试用例: 输入:1 2 3 4 5,期望输出2,因为只能选两个元素形成波动。但根据上述方法,初始up=1,down=1。第二个元素2>1,up=1+1=2,down=1。第三个元素3>2,此时比较up应为down+1,但down是1,所以up=2,保持不变。最终max(2,1)=2,正确。 另一个测试用例:1,3,2,4,1。正确的最长序列是1,3,2,4,1,长度5。根据贪心方法: 初始up=1, down=1. i=1(3>1): up=down+1=2, down=1. i=2(2<3): down=up+1=3, up=2. i=3(4>2): up=3+1=4, down=3. i=4(1<4): down=4+1=5, up=4. 最终max=5,正确。 所以该方法是可行的。 接下来需要将这种思路转化为代码。需要注意的是,题目中的花是一排的,不能改变顺序,所以直接遍历数组即可。 因此,代码的大致结构是: 读取n和数组h。 如果n <=1,直接输出n. 否则,初始化up=1, down=1. 遍历i从1到n-1: 如果h[i]>h[i-1],则up = down +1. elif h[i]<h[i-1],则 down=up+1. else: 不变. 最后输出max(up, down). 这样就能得到正确结果,并且时间复杂度是O(n),空间O(1),适用于1e5的数据规模。 用户提到的类差分法可能是指类似的处理方式,但可能在一些边界条件处理上出现了问题,导致TLE。而正确的贪心方法应该能通过所有测试点。 总结,该题的正确解法是贪心策略,维护两个状态变量,线性时间内解决问题。</think>### P1970 花匠 解法思路 **题目分析** 题目要求将花坛中的花调整为**严格交替上升下降**的波动序列,求最长的可行序列长度。核心问题等价于寻找数组中的**最长严格摆动子序列**。 --- #### 解法思路:贪心策略 通过维护两个状态变量动态追踪波动趋势,时间复杂度$O(n)$,空间复杂度$O(1)$。 1. **状态定义** - `up`:以当前元素结尾且最后一步**上升**的最长序列长度 - `down`:以当前元素结尾且最后一步**下降**的最长序列长度 2. **状态转移** - 若当前元素 > 前一个元素: $$ \text{up} = \text{down} + 1 $$ - 若当前元素 < 前一个元素: $$ \text{down} = \text{up} + 1 $$ - 相等时不更新状态(严格波动要求) 3. **最终结果** $$ \text{答案} = \max(\text{up}, \text{down}) $$ --- #### 参考代码 ```python n = int(input()) h = list(map(int, input().split())) if n <= 1: print(n) else: up, down = 1, 1 for i in range(1, n): if h[i] > h[i-1]: up = down + 1 elif h[i] < h[i-1]: down = up + 1 print(max(up, down)) ``` --- #### 关键点说明 - **贪心有效性**:每次波动变化时更新状态,保证最长序列的局部最优性[^1]。 - **严格波动处理**:仅当相邻元素严格不等时更新状态,符合题意要求。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值