引入
回忆一下,以前学动态规划是的最长不下降子序列,我们是如何地推的呢???
First
2 n 2^n 2n暴力,判断每个是否选择输出最大值。
Second
记忆化搜索,时间复杂度待定。
Third
n 2 n^2 n2动态规划,代码如下。
#include<bits/stdc++.h>
using namespace std;
int a[1010],n,f[1010],ans;
int main()
{
scanf("%d",&n);
for (int i = 1;i <= n;++i)
scanf("%d",a + i);
for (int i = 1;i <= n;++i)
{
f[i] = 1;
for(int j = 1;j < i;++j)
{
if (a[j] <= a[i] && f[i] < f[j] + 1)
f[i] = f[j] + 1;
}
if(f[ans] < f[i])
ans = i;
}
printf("%d\n",f[ans]);
return 0;
}
可是,当 n n n很大时, n 2 n^2 n2也过不了,那这么办呢???
思路
利用序列的单调性
对于任意一个单调序列,如 12345 1 2 3 4 5 12345(是单增的),若这时向序列尾部增添一个数 x x x,若 x > 5 x>5 x>5,增添成功,反之则失败。
由于普通代码是从头开始比较,而 x x x和 1 , 2 , 3 , 4 1,2,3,4 1,2,3,4 的大小比较是没有用处的,这种操作只会造成时间的浪费,所以效率极低。
优化方法就是新开一个数组 d,用它来记录每个序列的末尾元素,以求最长不下降为例, d k d_k dk 表示长度为k的不下降子序列的最小末尾元素。
我们用 c n t cnt cnt表示当前最长序列长度,也就是当前 d d d中的最后那个位置。
于是对于每个 a i a_i ai我们都找到一个刚刚比它大的 d i d_i di,可以证明这一定是最优解。
输出最后 c n t cnt cnt。
朴素时间
O ( N 2 ) O(N^2) O(N2)
二分优化:
当我们再找对于每个 a i a_i ai我们都找到一个刚刚比它大的 d i d_i di 时,我们可以利用单调性二分。
O ( N ∗ l o g ( n ) ) O(N*log(n)) O(N∗log(n))
代码:
for(int i=1;i<=n;i++){
int t=lower_bound(d,d+cnt+1,a[i]) - d;
if(t>cnt) cnt=t;
d[t]=a[i];
}//最后输出cnt
输出序列:
for(int i=1;i<=n;i++){
int t=lower_bound(d,d+cnt+1,a[i]) - d;
if(t>cnt) cnt=t;
f[i]=t;//f数组代表第i个位结尾的最长不下降子序列长度
d[t]=a[i];//当长度最大时记录d数组就是序列
}
例题:
【NOIP2013模拟联考10】独立集(bubble)