一 问题描述
最长递增子序列,也称最长上升/不下降子序列。
设L=<a1,a2,…,an>是n个不同的实数的序列,L的递增子序列是这样一个子序列Lin=<aK1,ak2,…,akm>,其中k1<k2<…<km且aK1<ak2<…<akm。
求最大的m值。
比如 <1, 3, 4, 2, 7>,的最长上升子序列为<1, 3, 4, 7>,m为4;
二 算法描述
- 把待求序列L=<a1,a2,…,an>与<0,1,2,4,....,n>求最长公共子序列,时间复杂度为O(n2)
- 动态规划,设dp[i]为以a[i]为结尾的最长公共子序列长度,则,dp[i] = max{dp[j]} + 1, 其中 0 < j < i,最后遍历一遍dp数组,最大值就是结果。时间复杂度为O(n2)时间。
- 改进第二种算法,引入一个数组f,其中f[i]表示长度为i的所有递增子序列中,尾元素最小值。显然,f[]是递增的。这样在更新dp[i]的时候,对于f[1], ...f[j], 从后向前扫描,找到第一个比a[i]小的f[j],那么,dp[i] = j + 1。鉴于f[i]是递增的,可以用二分查找,总时间复杂度为O(nlog(n))
三 代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int a[500001];
int dp[500001];
int f[500001];
int binary_search(int k, int l, int h)
{
int mid;
while (l <= h)
{
mid = (l + h)/2;
if (f[mid] <= k)
l = mid + 1;
else
h = mid - 1;
}
return h;
}
int main()
{
freopen("in.txt", "r", stdin);
int n;
int t = 0;
while (scanf("%d", &n) != EOF)
{
++t;
memset(f, -1, sizeof(f));
int i;
int tmp1,tmp2;
for (i = 0; i < n; ++i)
{
scanf("%d %d", &tmp1, &tmp2);
a[tmp1] = tmp2;
}
dp[1] = 1;
f[1] = a[1];
int len = 1;
for (i = 2; i <= n; ++i)
{
int x = binary_search(a[i], 1, len);
dp[i] = x + 1;
if ( f[i] == -1 || a[i] < f[dp[i]] )
f[dp[i]] = a[i];
if (dp[i] > len)
len = dp[i];
}
if (len == 1)
printf("Case %d:\nMy king, at most %d road can be built.\n\n", t, len);
else
printf("Case %d:\nMy king, at most %d roads can be built.\n\n", t, len);
}
return 0;
}