【DP|LIS】POJ-2533 Longest Ordered Subsequence(O(n^2)、O(nlogn))

69 篇文章 0 订阅
20 篇文章 0 订阅
Longest Ordered Subsequence
Time Limit: 2000MS Memory Limit: 65536K
   

Description

A numeric sequence of  ai is ordered if  a1 <  a2 < ... <  aN. Let the subsequence of the given numeric sequence ( a1a2, ...,  aN) be any sequence ( ai1ai2, ...,  aiK), where 1 <=  i1 <  i2 < ... <  iK <=  N. For example, sequence (1, 7, 3, 5, 9, 4, 8) has ordered subsequences, e. g., (1, 7), (3, 4, 8) and many others. All longest ordered subsequences are of length 4, e. g., (1, 3, 5, 8).

Your program, when given the numeric sequence, must find the length of its longest ordered subsequence.

Input

The first line of input file contains the length of sequence N. The second line contains the elements of sequence - N integers in the range from 0 to 10000 each, separated by spaces. 1 <= N <= 1000

Output

Output file must contain a single integer - the length of the longest ordered subsequence of the given sequence.

Sample Input

7
1 7 3 5 9 4 8

Sample Output

4

Source

Northeastern Europe 2002, Far-Eastern Subregion

————————————————————无力的分割线————————————————————

O(n^2)思路:这是DP的基础题。最长上升子序列问题(LIS)。先给出O(n^2)的思路和代码。

一开始看到白书上写的“设d[ i ]为以 i 结尾的最长上升子序列长度,则d[ i ] = max(0, d[ j ] | j < i, Aj < Ai) + 1”的时候,简直无爱了。第一不知道这个状转方程是怎么来的,第二不知道那个0是为什么。

然后就上网找代码看。理一下思路,应当是这样的。

首先是初始化,对于每个数字自己作为上升子序列的时候,d[ ]值是1。然后,对于每一个 i,即前 i 个数字枚举每一个 j,首先要符合“上升”条件,符合条件之后,进行状态转移,要怎么转移呢?

每个数字只有一个状态,那就是前 i 个数字能构成的最长上升子序列。这个状态要从前 i-1 个数字能构成的最长上升子序列转移过来吗?然而,不同数字参与构成的最长子序列又不一样,你并不知道哪个会是最优。例如:1、7、3、5、9,看到数字3的时候,7参与的是1、7,3参与的是1、3,看到数字9的时候,1、3、5、9比1、7、9更长。

必须从第一个开始直到第 i 个,每个数字的状态都转移一次,(状转方程)即:

For j: 0 ~ i-1
    if(sq[j] < sq[i])
        dp[i] = max{dp[i], dp[j] + 1}
代码如下:

/****************************************/
 #include <cstdio>
 #include <cstdlib>
 #include <cstring>
 #include <algorithm>
 #include <cmath>
 #include <stack>
 #include <queue>
 #include <vector>
 #include <map>
 #include <string>
 #include <iostream>
 using namespace std;
/****************************************/
int sq[1005], dp[1005];
int main()
{
	int n;
	while(scanf("%d", &n) != EOF) {
		int ans = 0;
		for(int i = 0;i < n;i++) {
			scanf("%d", &sq[i]);
			dp[i] = 1;
			for(int j = 0;j < i;j++) {
				if(sq[j] < sq[i])
					dp[i] = max(dp[i], dp[j] + 1);
			}
			if(ans < dp[i])
				ans = dp[i];
		}
		printf("%d\n", ans);
	}
	return 0;
}

接下来是期待已久的O(nlogn)的方法。

O(nlogn)思路:

参考大白书的思路,第一种方法其实是有重复劳动的。对于每一个数字 i,都要看前 i 个数是否比它小、从它转移过来是否更优。这种枚举没有注意到子序列中有些数根本不必转移。比如序列1、6、2、3、7,看到数字7的时候,只需要从{1、2、3}转移过来即可,{1、6}这个序列早就该丢弃了。因为对于{1、6}、{1、2}这两个序列,长度一样但是2比6小,这意味着{1、2}将来一定会比{1、6}更优才对。(比6大就一定比2大,比2大却不一定比6大,{1、2}更有可能成为最长)

换句话说,对于相同长度的序列,只需保存Ai最小的,越小越优。

这个时候,我们需要记录当前“最优序列”,这就用到了最优子结构的思想。

怎样记录和更新这个有序的“最优序列”g[ ]呢?二分法。而且应当是二分求下界。(想一想)

初始化g[ ]为无穷大。答案len = 0。接下来枚举数组,每次二分下界,算出“最优序列”个数,更新,然后更新g[ ]。状转方程变成

ans = max{ans, 最优子序列数的个数}

代码如下:

/****************************************/
 #include <cstdio>
 #include <cstdlib>
 #include <cstring>
 #include <algorithm>
 #include <cmath>
 #include <stack>
 #include <queue>
 #include <vector>
 #include <map>
 #include <string>
 #include <iostream>
 using namespace std;
/****************************************/
const int N = 1005, SUP = 2e9;
int sq[N], g[N];

int main()
{
	int n;
	while(scanf("%d", &n) != EOF) {
		for(int i = 1; i <= n; i++) {
			scanf("%d", &sq[i]);
			g[i] = SUP;
		}
		int len = 0;
		for(int i = 1;i <= n;i++) {
			int k = lower_bound(g+1, g+n+1, sq[i]) - g;//下界地址减去g[0]的地址,得到最优序列数的个数
			len = max(len, k);
			g[k] = sq[i];
		}
		printf("%d\n", len);
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值