题目链接:
[HDU 5489]Removed Interval[LIS]
此时最长上升子序列长度 = 4(4,5,6,8) + 2(1,3) = 6。
题意分析:
求含有N个元素的数组,去掉L个连续的元素后,剩下元素构成的最长上升子序列的长度。
解题思路:
我们就可以枚举这个长度为L的区间,从左往右滑动窗口。每向右移动一格,此时有:
最长上升子序列长度 = 窗口右边以右边第一个元素开头的最长上升子序列 + 窗口左边最大元素小于窗口右边第一个元素的最长上升子序列。
例如:
1 4 3 [5 7 4 9] 4 5 6 8
此时最长上升子序列长度 = 4(4,5,6,8) + 2(1,3) = 6。
如何求右边第一个元素开头的最长上升子序列呢?答案就是我们从右往左做LIS。
如何求左边呢?我们可以边移动窗口边做LIS,然后每次查询右边第一个元素在左边LIS中的位置,然后左边长度 = 位置 + 1。(因为我下标是从0开始XD)
个人感受:
原来我是想着先求出原序列的LIS,然后滑动窗口求它什么时候窗口里LIS内元素最小,结果GG。现在的是队友的解法,大赞~
具体代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 1e5 + 111;
int a[MAXN], arr[MAXN], b[MAXN], dp[MAXN]; // arr:LIS dp:以这个开头的最长上升子序列
int main()
{
int t, kase;
for (int kase = scanf("%d", &t); kase <= t; ++kase)
{
int n, l;
printf("Case #%d: ", kase);
scanf("%d%d", &n, &l);
for (int i = 0; i < n; ++i)
{
scanf("%d", &a[i]);
b[i] = -a[i]; // 取相反数,这样可以从后往前LIS
}
int x;
memset(arr, 0x7f, sizeof arr);
for (int i = n - 1; i >= l; --i)
{
x = lower_bound(arr, arr + n, b[i]) - arr;
arr[x] = b[i];
dp[i] = x + 1; // 长度等于下标 + 1
}
int ans = 0, mxlen = 0;
memset(arr, 0x7f, sizeof arr);
for (int i = l; i < n; ++i)
{
x = lower_bound(arr, arr + n, a[i]) - arr; //从左边的LIS查找
ans = max(ans, x + 1 + dp[i] - 1); // 之所以减一是因为我们从左边查找的是大于等于的
x = lower_bound(arr, arr + n, a[i - l]) - arr; // 左边更新LIS
arr[x] = a[i - l];
mxlen = max(mxlen, x + 1);
}
ans = max(ans, mxlen); //最后要比较下滑动窗口滑动到最后的情况
printf("%d\n", ans);
}
return 0;
}