[数据结构] UVa1471 Defense Lines 防线

题目

这里写图片描述
这里写图片描述

思路

1.需明确,在删除之后,最长连续递增子序列一定会存在在删除后两端连接处。下文用L代表最长连续递增子序列。
算法1:枚举起点j,终点i,再计算两点之间的L。 O ( n 3 )
算法2:预先打表g(i),f(i),分别代表以i结尾和开头的最长L,再枚举i和j,可以用 O ( 1 ) 时间算出L。 O ( n 2 )
算法3:只枚举终点i而不枚举起点j,通过一个STL set,查找最优j来避免枚举。 O ( n l o g n )


2.具体方法:规定(A[i], g)为set中的元素,A[i]为序列中实际元素值,g就是g(i),并由A[i]升序排序。

  • A[i]存在的目的是,若要构成连续子序列,则起点j的元素值必须小于终点i的。
  • 删减元素:对于 A [ i 2 ] <= A [ i 1 ] g [ i 2 ] > g [ i 1 ] i 1 ,可以进行删除。原因是, i 2 i 1 对终点元素值的要求更小,并且L更大。

枚举终点i时,只需要在set中lower_bound找到满足A[j] < A[i]的最大A[j]即可,原因是根据上面的删减规则,A[x]越大一定有g[x]越大。

代码

#include <cstdio>
#include <cstdlib>
#include <set>
#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 + 1000;
struct node {
    int a, g;
    node(int a, int g) :a(a), g(g) {}   // 带参构造函数的写法
    bool operator < (const struct node &rhs) const {
        return a < rhs.a;
    }
};
int A[maxn], n, ans, G[maxn], F[maxn];
set<node> B;

int main() {
    //freopen("input.txt", "r", stdin);
    //freopen("output.txt", "w", stdout);
    int T;
    scanf("%d", &T);
    while (T--) {
        scanf("%d", &n);
        _for(i, 0, n) scanf("%d", &A[i]);

        G[0] = 1;
        _for(i, 1, n) {
            if (A[i] > A[i - 1]) G[i] = G[i - 1] + 1;
            else G[i] = 1;
        }
        F[n-1] = 1;
        for (int i = n - 2; i >= 0; i--) {
            if (A[i] < A[i + 1]) F[i] = F[i + 1] + 1;
            else F[i] = 1;
        }

        ans = 1;
        B.clear();
        B.insert(node(A[0], G[0]));
        _for(i, 1, n) {
            node now(A[i], G[i]);
            set<node>::iterator iter = B.lower_bound(now);
            bool keep = true;
            if (iter != B.begin()) {
                --iter;
                ans = max(ans, F[i] + iter->g);   //学会对iterator直接进行操作
                if (iter->g >= now.g) 
                    keep = false;
            }
            if (keep) {
                iter = B.insert(now).first;
                if (iter != B.end()) {
                    for (++iter; iter != B.end();) {
                        if (now.g >= iter->g && now.a < iter->a) B.erase(iter++);   // 此处a<a是为了防止等于发生?
                        else break;
                    }
                }
            }
        }

        printf("%d\n", ans);
    }
    return 0;
}

码力技巧

1.struct的构造函数的写法:

struct node {
    int a, g;
    node(int a, int g) :a(a), g(g) {}   // 带参构造函数的写法
    bool operator < (const struct node &rhs) const {
        return a < rhs.a;
    }
};

2.aoapc习题选解,陈锋大佬教给的for循环宏定义:

#define _for(i, a, b) for (int i = (a); i < (b); i++)
#define _rep(i, a, b) for (int i = (a); i <= (b); i++)

使用起来

_for(i, 0, n) scanf("%d", &A[i]);

可能还是有一点增加代码易读性的功能的吧。。。
反正我只见他的代码这样写过。。

本题资源

此题是 ACM/ICPC Central European Regional Contest 区域赛的题,我找到了官方题解和数据,拿给大家用啦:
http://cerc10.ii.uni.wroc.pl/solutions.html
data

废话

1.卡了我半个月的题。。终于过了。。至于为什么能卡我半个月,说不上来。这道题吧,说难也不难,特别是LRJ已经清清楚楚地把思路讲出来了,不过是稍抽象一点。还是码力不够,做题做得少。而且应该了解到,一定要思路想的清清楚楚再去写代码,我之前做的时候,是把set整个都做完以后,才从1开始枚举i,这样就导致我还必须去判断一下终点i的位置是不是在起点j后面,不然就逻辑错误导致花式WA。最后一看LRJ的代码,明明set和枚举i可以在一个循环里同时做。。。
2.而且之前程序虽然绕,但整体都对,出了g(i)和f(i)的求法。但我全程都以为,这么简单的两个打表我怎么能错。。而且错的还很隐晦(现在都不知道那种是怎么错的)。。导致一直忽略这两个打表的debug,一直在枚举i和set上下功夫,下了半天没用的功夫。。
之前的打表代码,懒得看为啥错了。。。

int s = 0; G[0] = 1;
_for(i, 1, n) {
    if (A[i] < A[i - 1]) {
        s = i;
        G[i] = 1;
    }
    else G[i] = i - s + 1;
}
s = n - 1; F[n - 1] = 1;
for (int i = n - 2; i >= 0; i--) {
    if (A[i] > A[i + 1]) {
        s = i;
        F[i] = 1;
    }
    else F[i] = s - i + 1;
}

猜你喜欢

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