[线性DP] 洛谷P1020 导弹拦截 (模型:LIS最长上升子序列)

题目

题目

模型

这里写图片描述
LIS的解法,大体来说有两种:

线性DP

1.状态定义:d(i),以位置i的元素结尾的LIS长度。
2.初状态:d[1…n] = 1。
3.答案:d[n]。
4.状态转移方程:

d ( i ) = m a x { 1 , d ( j ) + 1 | j < i , A j < A i }

5.复杂度: O ( n 2 )

单调栈

使用数组B[1…],B[i]表示长度为i的LIS的最小尾元素值。
此处用到了贪心,即对于同一个长度的LIS,尾元素越小,越容易后面再加元素。
遍历A[1…n]维护B[1…],维护方法为:

  • 当前元素A[i]>B[尾]时,插入;
  • 当前元素A[i]<=B[尾]时,寻找第一个大于等于A[i]的B[j],令B[j] = A[i]。

此处的寻找用二分查找,因为
复杂度: O ( n l o g n )
(遍历为n,二分查找为logn)

思路

本题稍微理解一下就知道,第一小问是求最长下降子序列,第二小问是求最长上升子序列,模型裸题。
洛谷用n2算法100分,nlogn算法200分。

代码

线性DP

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;

const int maxn = 200000 + 100;
int n, A[maxn], d[maxn];

int main() {
    n = 0;
    while (scanf("%d", &A[n]) == 1) n++;

    _for(i, 0, n) d[i] = 1;
    _for(i, 0, n)
        for (int j = i - 1; j >= 0; j--)
            if (A[i] <= A[j])
                d[i] = max(d[i], d[j] + 1);

    int ans = 1;
    _for(i, 0, n) ans = max(ans, d[i]);
    printf("%d\n", ans);

    _for(i, 0, n) d[i] = 1;
    _for(i, 0, n)
        for (int j = i - 1; j >= 0; j--)
            if (A[i] > A[j])
                d[i] = max(d[i], d[j] + 1);

    ans = 1;
    _for(i, 0, n) ans = max(ans, d[i]);
    printf("%d\n", ans);

    return 0;
}

单调栈

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <functional>
#include <algorithm>
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;

const int maxn = 100000 + 100;
int A[maxn], B[maxn], n, len;

int main() {
    n = 1;
    while (scanf("%d", &A[n]) == 1) n++;
    n--;

    len = 1;
    B[1] = A[1];
    _rep(i, 2, n) {
        int *p = upper_bound(B + 1, B + len, A[i], greater<int>());
        if (*p >= A[i]) {
            len++;
            B[len] = A[i];
        }
        else  *p = A[i];
    }
    printf("%d\n", len);

    len = 1;
    memset(B, 0, sizeof(B));
    B[1] = A[1];
    _rep(i, 2, n) {
        int *p = lower_bound(B + 1, B + len, A[i]);
        //if (*p != A[i]) p++;  // p是第一个大于等于A[i]的元素所在位置
        if (*p < A[i]) {
            len++;
            B[len] = A[i];
        } else  *p = A[i];
    }
    printf("%d\n", len);

    return 0;
}

码农小技巧

lower_bound()和upper_bound在本题再次引起注意,因为这两个分不清瞎用导致本题卡了2h。。
upper_bound:返回的是键值为i的元素插入而不破坏容器顺序的最后一个位置。
lower_bound:返回的是键值为i的元素插入而不破坏容器顺序的第一个位置。
举例子:

  1. set里没有元素i的时候,两个元素的返回值是一样的。 1 2 4 5 这个序列,upp(3)和low(3)都返回位置2(下标)。
  2. 如果只有一个元素i,low返回那个元素的位置,而upp返回那个元素的位置的后一个位置。 1 2 4 5这个序列upp(2)返回下标2而low(2)返回下标1。
  3. 多个元素i,low返回那个元素的位置,upp返回那多个元素中的最后一个的后一个位置。 1 2 2 4 5 这个序列upp(2)返回下标3的位置,low(2)返回下标1的位置。

注意这里的后一个位置即可。

废话

好久没写题了,考试考的心乱,间隔了一个周多。
接下来的暑假写题写爆。
坐标大连海事大学,在此上自习,希望效率能高些。

猜你喜欢

转载自blog.csdn.net/icecab/article/details/80925853