dp_基础_最长上升子序列

也是非常经典的一道题
比较简单的做法是:
设dp[i] i: 以nums[i]作为一个子数组的,最长的上升子序列
则有
无后效性:i肯定不会影响i之前的。
子问题重叠:算i+1,i+2 .. n 时,都需要dp[i]
最优子结构:for( int j=0;j<i;++j ) if ( nums[j] > nums[i] ) dp[i] = max(dp[i],dp[j]+1);

int ret = 0;
for( int i=0;i<n;++i ){
	scanf("%d",&nums[i]);

	for( int j=0;j<i;++j ){
		if ( nums[j] < nums[i] ){
			dp[i] = max(dp[i],dp[j] + 1);
		}
	}

	ret = max(dp[i]+1,ret);
}
printf("%d\n",ret);

但是,当 n>=100000 时,很容易超时


所以需要 更高级的算法
设dp[i] i:最长上升子序列长度等于i时,最后一个数字。当然,有可能会更新dp[i]的值。(取最小值)
则有
无后效性:i肯定不会影响i之前的。又因为数组的顺序,就算修改了dpx 的值,也不会影响dp[i]的值。
子问题重叠:需要用二分法来计算当前值为 i 的最后值。
最优子结构:*lower_bound(dp,dp+n,nums[i]) = nums[i];
http://poj.org/problem?id=3903 ac代码

#define NMAX 100005

int nums[NMAX];
int dp[NMAX];

int main(){
	int n;
	while(~scanf("%d",&n)){
		memset(dp,0x3f,sizeof(dp));
		for( int i=0;i<n;++i ){
			scanf("%d",&nums[i]);
			*lower_bound(dp,dp+n,nums[i]) = nums[i];
		}
		int ret = 0;
		while(dp[ret] <INF ) ++ret;
		printf("%d\n",ret);
	}

	return 0;
}

注:lower_bound() 函数用于在指定区域内查找不小于目标值的第一个元素。即:有可能等于
upper_bound() 函数用于在指定范围内查找大于目标值的第一个元素。即:只能是大于

所以,如果是使用了函数返回的下标的话,insert它, lower_bound() 会删掉重复的 upper_bound() 则不会。
因题意是,递增子序列,所以用lower_bound
如果是,非下降子序列,则用upper_bound

posted @ 2022-05-06 19:57  传说中的水牛  阅读(24)  评论(0编辑  收藏  举报