题目
思路
1.需明确,在删除之后,最长连续递增子序列一定会存在在删除后两端连接处。下文用L代表最长连续递增子序列。
算法1:枚举起点j,终点i,再计算两点之间的L。
O(n3)
O
(
n
3
)
算法2:预先打表g(i),f(i),分别代表以i结尾和开头的最长L,再枚举i和j,可以用
O(1)
O
(
1
)
时间算出L。
O(n2)
O
(
n
2
)
算法3:只枚举终点i而不枚举起点j,通过一个STL set,查找最优j来避免枚举。
O(nlogn)
O
(
n
l
o
g
n
)
2.具体方法:规定(A[i], g)为set中的元素,A[i]为序列中实际元素值,g就是g(i),并由A[i]升序排序。
- A[i]存在的目的是,若要构成连续子序列,则起点j的元素值必须小于终点i的。
- 删减元素:对于 A[i2]<=A[i1]且g[i2]>g[i1] A [ i 2 ] <= A [ i 1 ] 且 g [ i 2 ] > g [ i 1 ] 的 i1 i 1 ,可以进行删除。原因是, i2 i 2 比 i1 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;
}