[題解] [NOIP 1999] 導彈攔截

wxy3265發表於2024-04-18

[NOIP 1999] 導彈攔截

題目描述

有若干枚導彈,每一枚導彈的高度是 \(h_i\) ,導彈攔截系統每次攔截導彈都不能比上一次攔截的高度更高,導彈攔截沒有冷卻時間且第一次攔截的高度任意。

問題1:一套系統最多能攔截多少導彈?

問題2:攔截所有導彈最少需要多少個攔截系統?

輸入格式

一行,若干個整數,表示 \(h_i\)

輸出格式

兩行,分別表示兩個問題的答案

題解

對於問題1,實際就是求最長不上升子序列的長度,使用動態規劃解決。樸素的動態規劃是設定 \(dp_i\) 表示以 \(h_i\) 結尾的子序列最大長度。轉移時尋找 \(i\) 之前的最大長度轉移,即 \(dp_i = max(dp_j), j \leq i 且 h_j \geq h_i\) 。這種演算法的時間複雜度是 \(O(n^2)\) ,無法透過。

考慮最佳化。對每個導彈的列舉顯然無法最佳化,考慮最佳化狀態轉移。該演算法的瓶頸在於尋找最大長度時需要列舉,但符合高度限制的導彈分佈是零散的,無法根據編號快速查詢,因此考慮改變查詢的維度。我們的目標是獲得以 \(h_i\) 結尾的子序列的最大長度,判斷是否能往一個序列上延長只需要考慮該序列的結尾元素,而不難得出最大長度會隨著子序列結尾的數的減小而增大。換句話說,在取得子序列最大長度的條件下(不論它們的長度是多少),不可能存在h的子序列序列A的結尾元素比子序列B的結尾元素大並且A的長度比B長的情況。因此我們只需要統計出每個長度的不上升子序列的結尾元素即可,在最優情況下,這個結尾元素應該取可能的情況中的最大值。如此我們就得到了一個有序的,可以 \(O(1)\) 驗證可行性的序列,採用二分即可在 \(O(log(n))\) 複雜度內找到最優的狀態轉移。

對於問題2,實際上就是求最長不上升子序列的最少個數,即最長不上升子序列的最小劃分。根據 Dilworth 定理,即對於任意有限偏序集,其最大反鏈中元素的數目必等於最小鏈劃分中鏈的數目。在本題中的應用是:最長不上升子序列的最小劃分數(個數)等於最長上升子序列的長度。因此在第一問的基礎上修改得到最長上升子序列的長度即可解決此題。具體的修改是將結尾元素的最大值替換為最小值、驗證可行性從不高於變成高於。

AC程式碼

#include <iostream>
#include <algorithm>
using namespace std;

const int MAXN = 1e5 + 3;

int n, a[MAXN], dp[MAXN], f[MAXN];

int main() {
    while (cin >> a[++n]);
    // 求最長不升子序列
    for (int i = 0; i <= n; i++) f[i] = 0;
    n--;
    int ans1 = 0;
    dp[1] = 1, f[1] = a[1];
    for (int i = 2; i <= n; i++) {
        int l = 1, r = n, mid;
        while (l <= r) {
            mid = (l + r) >> 1;
            if (f[mid] >= a[i]) l = mid + 1;
            else r = mid - 1;
        }
        f[r + 1] = max(f[r + 1], a[i]);
        dp[i] = r + 1;
        ans1 = max(ans1, dp[i]);
    }
    cout << ans1 << '\n';
    // 求最長上升子序列
    int ans2 = 0;
    for (int i = 0; i <= n; i++) f[i] = 1e6;
    dp[1] = 1, f[1] = a[1];
    for (int i = 2; i <= n; i++) {
        int l = 1, r = n, mid;
        while (l <= r) {
            mid = (l + r) >> 1;
            if (f[mid] < a[i]) l = mid + 1;
            else r = mid - 1;
        }
        f[r + 1] = min(f[r + 1], a[i]);
        dp[i] = r + 1;
        ans2 = max(ans2, dp[i]);
    }
    cout << ans2 << '\n';
    return 0;
}

相關文章