题目描述
在最后一次战争摧毁了你的国家之后,作为阿德尼亚王国的国王,你决定是时候加强首都的防御了。你的防御工事的一部分是一排法师塔,从城市附近开始一直延伸到北部森林。你的顾问们确定防御质量只取决于一个因素:最长的连续递增塔高序列的长度。
经过一些艰难的谈判,建造新塔已经不可能了。不过,阿德尼亚的法师们同意拆除一些塔。你可以拆除任意数量的塔,但法师们提出了一个条件:这些塔必须是连续的。
例如,如果塔的高度分别是 5,3,4,9,2,8,6,7,15, 3, 4, 9, 2, 8, 6, 7, 15,3,4,9,2,8,6,7,1,那么通过拆除高度为 9,2,89, 2, 89,2,8 的塔,最长的连续递增塔序列是 3,4,6,73, 4, 6, 73,4,6,7。
题目分析
问题理解
我们需要在删除一段连续塔楼后,找到剩余塔楼序列中最长的连续递增子序列的长度。关键约束是:
- 只能删除一段连续的塔楼(可以不删除)
- 删除后剩下的塔楼保持原来的相对顺序
- 需要找到最长的连续递增子序列
输入输出规格
- 输入 :多个测试用例,第一行是测试用例数量 Z≤25Z \leq 25Z≤25,每个测试用例包含塔的数量 n≤2×105n \leq 2 \times 10^5n≤2×105 和塔的高度序列
- 输出:每个测试用例输出一个整数,表示通过删除一段连续塔楼后能得到的最长连续递增序列的长度
解题思路
关键观察
- 基础情况:如果不删除任何塔楼,答案就是原序列的最长连续递增子序列长度
- 连接两个递增序列:通过删除中间的一段塔楼,我们可以将两个递增序列连接起来,条件是前一个序列的最后一个元素小于后一个序列的第一个元素
算法设计
步骤1:预处理数组
定义两个辅助数组:
- f[i]f[i]f[i]:以第 iii 个塔结尾的最长连续递增子序列长度
- g[i]g[i]g[i]:以第 iii 个塔开头的最长连续递增子序列长度
计算方式:
- f[0]=1f[0] = 1f[0]=1,对于 i>0i > 0i>0:如果 a[i]>a[i−1]a[i] > a[i-1]a[i]>a[i−1],则 f[i]=f[i−1]+1f[i] = f[i-1] + 1f[i]=f[i−1]+1,否则 f[i]=1f[i] = 1f[i]=1
- g[n−1]=1g[n-1] = 1g[n−1]=1,对于 i<n−1i < n-1i<n−1:如果 a[i]<a[i+1]a[i] < a[i+1]a[i]<a[i+1],则 g[i]=g[i+1]+1g[i] = g[i+1] + 1g[i]=g[i+1]+1,否则 g[i]=1g[i] = 1g[i]=1
步骤2:考虑三种情况
- 不删除任何塔楼 :答案为 max(f[i])\max(f[i])max(f[i])
- 删除单个塔楼 :对于位置 iii,如果 a[i]<a[i+2]a[i] < a[i+2]a[i]<a[i+2],可以连接 f[i]f[i]f[i] 和 g[i+2]g[i+2]g[i+2]
- 使用数据结构优化查找 :对于每个位置 iii,找到 j<ij < ij<i 使得 a[j]<a[i]a[j] < a[i]a[j]<a[i] 且 f[j]f[j]f[j] 最大
步骤3:使用 set\texttt{set}set 维护候选解
维护一个 set\texttt{set}set,其中存储 (a[j],f[j])(a[j], f[j])(a[j],f[j]) 对,并保证:
- 随着 a[j]a[j]a[j] 增加,f[j]f[j]f[j] 也严格增加
- 这样可以快速二分查找满足 a[j]<a[i]a[j] < a[i]a[j]<a[i] 的最大 f[j]f[j]f[j]
算法复杂度
- 时间复杂度 :O(nlogn)O(n \log n)O(nlogn),主要来自 set\texttt{set}set 操作
- 空间复杂度 :O(n)O(n)O(n),用于存储 fff 和 ggg 数组
代码实现
cpp
// Defense Lines
// UVa ID: 1471
// Verdict: Accepted
// Submission Date: 2025-10-19
// UVa Run Time: 0.440s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200005;
int a[MAXN], f[MAXN], g[MAXN];
int main() {
int Z;
scanf("%d", &Z);
while (Z--) {
int n;
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%d", &a[i]);
}
// 计算f[i]: 以i结尾的最长连续递增子序列长度
f[0] = 1;
for (int i = 1; i < n; i++) {
if (a[i] > a[i-1]) f[i] = f[i-1] + 1;
else f[i] = 1;
}
// 计算g[i]: 以i开头的最长连续递增子序列长度
g[n-1] = 1;
for (int i = n-2; i >= 0; i--) {
if (a[i] < a[i+1]) g[i] = g[i+1] + 1;
else g[i] = 1;
}
int ans = 0;
// 情况1: 不删除任何塔楼
for (int i = 0; i < n; i++) {
ans = max(ans, f[i]);
}
// 情况2: 删除单个塔楼
for (int i = 0; i < n - 2; i++) {
if (a[i] < a[i+2]) {
ans = max(ans, f[i] + g[i+2]);
}
}
// 情况3: 使用set优化查找
set<pair<int, int>> s; // 存储(a[j], f[j])对
for (int i = 0; i < n; i++) {
if (i > 0) {
// 在set中查找满足a[j] < a[i]的最大f[j]
auto it = s.lower_bound({a[i], -1});
if (it != s.begin()) {
it--;
ans = max(ans, it->second + g[i]);
}
}
// 维护set,删除被支配的元素
auto it = s.lower_bound({a[i], -1});
bool should_insert = true;
// 如果存在相同a值但f值更大的元素,不插入当前元素
if (it != s.end() && it->first == a[i]) {
if (it->second >= f[i]) {
should_insert = false;
} else {
s.erase(it);
}
}
if (should_insert) {
// 插入当前元素
it = s.insert({a[i], f[i]}).first;
// 检查前一个元素,如果前一个元素的f值更大,删除当前元素
if (it != s.begin()) {
auto prev_it = it;
prev_it--;
if (prev_it->second >= it->second) {
s.erase(it);
should_insert = false;
}
}
// 删除后面被当前元素支配的元素
if (should_insert) {
auto next_it = it;
next_it++;
while (next_it != s.end() && next_it->second <= it->second) {
next_it = s.erase(next_it);
}
}
}
}
printf("%d\n", ans);
}
return 0;
}
总结
本题的关键在于理解如何通过删除一段连续塔楼来连接两个递增序列,并使用高效的数据结构来优化查找过程。通过预处理 fff 和 ggg 数组,我们可以快速计算基础情况,然后使用set来维护候选解集合,确保在 O(nlogn)O(n \log n)O(nlogn) 时间内解决问题。