【题解】低买

题目

题目描述

给定一段时间内股票的每日售价(正16位整数)。

你可以选择在任何一天购买股票。

每次你选择购买时,当前的股票价格必须严格低于你之前购买股票时的价格。

编写一个程序,确定你应该在哪些天购进股票,可以使得你能够购买股票的次数最大化。

例如,下面是一个股票价格时间表:

 Day   1  2  3  4  5  6  7  8  9 10 11 12
 
Price 68 69 54 64 68 64 70 67 78 62 98 87

如果每次购买都必须遵循当前股票价格严格低于之前购买股票时的价格,那么投资者最多可以购买四次该股票。

买进方案之一为:


Day    2  5  6 10

Price 69 68 64 62

输入格式

第1行包含整数 N,表示给出的股票价格的天数。

第2至最后一行,共包含 N 个整数,每行10个,最后一行可能不够10个,表示 N 天的股票价格。

输出格式

输出占一行,包含两个整数,分别表示最大买进股票次数以及可以达到最大买进次数的方案数。

如果两种方案的买入日序列不同,但是价格序列相同,则认为这是相同的方案(只计算一次)。

样例

样例1输入

12
68 69 54 64 68 64 70 67 78 62 98 87

样例1输出

4 2

样例2输入

5
4 3 2 1 1

样例2输出

4 1

数据范围与提示

1≤N≤5000

同一行数之间用空格隔开。


题解

让我们一问一问的解决

首先是第一问

LIS裸题,相信大家都看出来了

所以第一问就这样被我们轻松的解决了 :)

接下来是第二问

相信有很多同学(包括我之前)的想法都是先求出 d p dp 数组,然后遍历两遍,一遍找子序列的最长长度 m a x l maxl ,一遍找有多少个 d p i = m a x l dp_i=maxl

这是最好想到的方法,但有很多漏洞,比如

如果两条子序列的长度相同都为 m a x l maxl ,且结尾相同,但前面都不相同,那么在遍历第二遍时就会把它算作一种情况

那么又有同学会想到用GM讲过的输出序列的思想,但这样时间复杂度不说(貌似也过得了),一样面临着状态覆盖的问题,所以这也不行

所以我的想法是重新维护一个 t t 数组,存储 a i a_i 结尾的最长下降子序列的方案总数(好绕-_-!),那么其实 t t 也类似于 L I S LIS 求解的:判断能否接在前面的某一段前面,如果能,则“继承”它的方案数;

这里还是有处理重复的问题:

1.头一样尾不一样:利用DP的思想,我们处理每个状态都必须是无后效性的,所以我们不妨把它们目前看做重复序列,那么把 t i t_i 赋为0。之后如果头继续相等,就继续执行1,否则就会执行2,将它们分开成两个序列(有点像递归呢)

2.头不一样:可以直接断定它们完全不同,按照上面说的方法做,这也完美的解决了1的问题

综上,我们已经完成了去 重,可以放心做了

代码

#include <iostream>
#include <cstdio>
using namespace std;
int n,a[5005],dp[5005],_max = 0,ans ,t[5005];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		dp[i]=1;
		for(int j=1;j<i;j++){
			if(a[j]>a[i]){
				dp[i]=max(dp[i],dp[j]+1);
			}
		}
		if(dp[i]>_max){
			_max=dp[i];
		}
		for(int j=1;j<i;j++){
			if(dp[i]==dp[j] && a[i]==a[j]){
				t[j]=0;
			} 
			else if(a[j]>a[i]&&dp[j]+1==dp[i]){
				t[i]+=t[j];
			}
		}
		if(t[i]==0) t[i]=1;
	}
	int cnt=0;
	for(int i=1;i<=n;i++){
		if(dp[i]==_max){
			cnt+=t[i];
		}
	}
	printf("%d %d",_max,cnt);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/tanfuwen_/article/details/106942048