最长上升(下降)子序列

例题:P1020 [NOIP1999 普及组] 导弹拦截

题目大意

用一个导弹拦截系统拦截导弹,该系统第一发炮弹能到达任意高度,随后每发炮弹不能高于前一发的高度,每个导弹有一个固定的高度。
问该系统最多能拦截多少导弹,以及拦截所有导弹需要多少个系统。

解题思路

第一问就是求数组的最长下降子序列。
f[i]表示以第i个数为结尾的最长下降子序列的长度。
那么容易得到转移方程:

dpi=max(1, max(dpj+1)j<i,h(j)h(i))

然而这种解法的复杂度为O(n2),并不能通过本题的所有数据。

显然那个状态转移方程是可以优化的,我们可以令f[i]表示长度为i的下降子序列最后一个元素的最大值,容易发现f[i]是单调不增的(反证法易证),这一我们就可以用二分查找判断每个元素应该接在哪个长度的后面。

第二问考虑贪心,对于每个导弹,如果有若干系统可以拦截它,那么应该选择系统中当前高度(该系统命中最后一个导弹的高度)最低的那个,这样能尽可能的减少浪费。我们用f[i]表示每个拦截系统,并将它们的当前高度从小到大排序,对于每个导弹,二分查找第一个可以拦截它的系统,容易发现更新f[i]之后其单调性仍然满足。

参考代码

#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
int h[N], f[N], t, cnt;
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    int a;
    while (cin >> a)
    {
        h[cnt++] = a;
    }
    for (int i = 0; i < cnt; ++i)
    {
        int l = 1, r = t, ans = 0;
        while (l <= r)
        {
            int mid = l + r >> 1;
            if (f[mid] >= h[i])
            {
                ans = mid;
                l = mid + 1;
            }
                
            else
                r = mid - 1;
        }
        f[ans + 1] = max(f[ans + 1], h[i]);
        t = max(t, ans + 1);
    }
    cout << t << endl;
    t = 0;
    memset(f, 0x3f, sizeof(f));
    for (int i = 0; i < cnt; ++i)
    {
        int l = 1, r = t, ans = 0;
        while (l <= r)
        {
            int mid = l + r >> 1;
            if (f[mid] < h[i])
            {
                ans = mid;
                l = mid + 1;
            }
                
            else
                r = mid - 1;
        }
        f[ans + 1] = min(f[ans + 1], h[i]);
        t = max(t, ans + 1);
    }
    cout << t;
    return 0;
}

容易发现两问的代码有极高的相似性,第二问其实是在计算最长严格上升子序列。这其实是Dilworth定理:将一个序列剖成若干单调不升子序列的最小个数等于该序列最长上升子序列的元素个数。

参考博客题解 P1020 【[NOIP1999 普及组] 导弹拦截】

posted @   何太狼  阅读(35)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
历史上的今天:
2022-02-05 背包问题
点击右上角即可分享
微信分享提示