UVa 1471 Defense Lines

题目描述

在最后一次战争摧毁了你的国家之后,作为阿德尼亚王国的国王,你决定是时候加强首都的防御了。你的防御工事的一部分是一排法师塔,从城市附近开始一直延伸到北部森林。你的顾问们确定防御质量只取决于一个因素:最长的连续递增塔高序列的长度

经过一些艰难的谈判,建造新塔已经不可能了。不过,阿德尼亚的法师们同意拆除一些塔。你可以拆除任意数量的塔,但法师们提出了一个条件:这些塔必须是连续的

例如,如果塔的高度分别是 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. 基础情况:如果不删除任何塔楼,答案就是原序列的最长连续递增子序列长度
  2. 连接两个递增序列:通过删除中间的一段塔楼,我们可以将两个递增序列连接起来,条件是前一个序列的最后一个元素小于后一个序列的第一个元素

算法设计

步骤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:考虑三种情况
  1. 不删除任何塔楼 :答案为 max⁡(f[i])\max(f[i])max(f[i])
  2. 删除单个塔楼 :对于位置 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]
  3. 使用数据结构优化查找 :对于每个位置 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(nlog⁡n)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(nlog⁡n)O(n \log n)O(nlogn) 时间内解决问题。

相关推荐
CHANG_THE_WORLD4 小时前
switch语句在汇编层面的几种优化方式 ,为什么能进行优化
汇编·算法·switch·汇编分析·switch case·switch case 汇编·switch case 语句
山,离天三尺三4 小时前
深度拷贝详解
开发语言·c++·算法
Blossom.1184 小时前
把AI“撒”进农田:基于极值量化与状态机的1KB边缘灌溉决策树
人工智能·python·深度学习·算法·目标检测·决策树·机器学习
一只鱼^_4 小时前
第 167 场双周赛 / 第 471 场周赛
数据结构·b树·算法·leetcode·深度优先·近邻算法·迭代加深
被制作时长两年半的个人练习生4 小时前
近期的笔试和面试的复盘
算法·面试·职场和发展·算子
gsfl5 小时前
贪心算法1
算法·贪心算法
小猪咪piggy6 小时前
【算法】day8 二分查找+前缀和
算法
Word码6 小时前
[排序算法]希尔排序
c语言·数据结构·算法·排序算法
前端小刘哥6 小时前
解析视频直播点播平台EasyDSS在视频点播领域的技术架构与性能优势
算法