算法刷题【洛谷P1233】木棍加工(最长上升子序列、最长下降子序列模板题)

异想之旅:本人原创博客完全手敲,绝对非搬运,全网不可能有重复;本人无团队,仅为技术爱好者进行分享,所有内容不牵扯广告。本人所有文章仅在CSDN、掘金和个人博客(一定是异想之旅域名)发布,除此之外全部是盗文!


洛谷 P1233 木棍加工(最长上升子序列)

题目描述

一堆木头棍子共有n根,每根棍子的长度和宽度都是已知的。棍子可以被一台机器一个接一个地加工。机器处理一根棍子之前需要准备时间。准备时间是这样定义的:

第一根棍子的准备时间为1分钟;

如果刚处理完长度为L,宽度为W的棍子,那么如果下一个棍子长度为Li,宽度为Wi,并且满足L>=Li,W>=Wi,这个棍子就不需要准备时间,否则需要1分钟的准备时间;

计算处理完n根棍子所需要的最短准备时间。比如,你有5根棍子,长度和宽度分别为(4, 9),(5, 2),(2, 1),(3, 5),(1, 4),最短准备时间为2(按(4, 9)、(3, 5)、(1, 4)、(5, 2)、(2, 1)的次序进行加工)。

输入格式

第一行是一个整数n(n<=5000),第2行是2n个整数,分别是L1,W1,L2,w2,…,Ln,Wn。L和W的值均不超过10000,相邻两数之间用空格分开。

输出格式

仅一行,一个整数,所需要的最短准备时间。

输入输出样例

In 1:

5
4 9 5 2 2 1 3 5 1 4

Out 1:

2
题解

解这道题有两个步骤:首先推出这道题的答案即最长下降子序列,然后求解即可。

第一步严谨的证明十分复杂,我就用一个符合直觉的描述来说服大家就好。信竞题没有必要全部严格证明。

我们先来看第一步:构造数据 ( 1 , 1 ) , ( 2 , 2 ) , ( 3 , 3 ) , ( 4 , 4 ) , ( 5 , 5 ) (1,1),(2,2),(3,3),(4,4),(5,5) (1,1),(2,2),(3,3),(4,4),(5,5),尝试按照柱状图的思路画出来他们(有点丑不要介意)

在这里插入图片描述

显然这些是可以一趟处理完的。进而不难发现,如果一些木棍可以一次处理完,当且仅当木棍在二维平面上表示成上面这样的状态后,在右边的木棍都比在左边的高(也就是排成一个顺序,对于任意 i < j i<j i<j,有 L i < L j L_i<L_j Li<Lj W i < W j W_i<W_j Wi<Wj

再看一种情况:

在这里插入图片描述

答案显而易见为 2 2 2

我们发现,事实上对于任意序列,答案就是我们根据 L i L_i Li W i W_i Wi 进行排序后,所有 W i W_i Wi 组成的序列中的最长下降子序列长度

由第一个例子得到的结论我们知道, L i < L j L_i<L_j Li<Lj W i > W j W_i>W_j Wi>Wj 的两个木棍不可能同批次加工,定然要分两组,因此这个结论满足必要性,也就是答案无论如何不可能比他小了。

下面证明充分性:

  • 对于子序列中的项右边的 ( L x , W x ) (L_x, W_x) (Lx,Wx)(本例中的 ( 5 , 5 ) (5,5) (5,5)),定然存在 i i i 在选出的子序列中,使 L x > L i L_x>L_i Lx>Li W x > W i W_x>W_i Wx>Wi ,所以在实际操作中把它放到 i i i 前方即可。
  • 如果这样的 x x x 多于 i i i ,则最长下降子序列应由所有的 x x x 组成,而非当前的 i i i
  • 事实上,对于 i i i 右边的 x x x ,都一定有一个 i i i 使它满足上述条件,否则这个 x x x 理应存在于选出的序列中

能不能看懂都就这样吧。证明真的比较麻烦。

下面来说如何求解最长下降子序列:

首先,对于复杂度为 n 2 n^2 n2 的算法不难想,就是对于每一个元素,寻找在此之前的比它大的元素中,已经组成的最长下降子序列的最大值,再加 1 保存为当前点结尾的最长下降子序列长度。

此处直接贴代码(求最长下降子序列的模板代码,非本题 AC 代码):

#include <bits/stdc++.h>
using namespace std;
const int N = 100001;
int n, a[N], dp[N];
int main() {
    cin >> n;  // 序列长度
    for (int i = 1; i <= n; i++) cin >> a[i];  // 输入的序列
    for (int i = 1; i <= n; i++) {
        dp[i] = 1;  // 初始值
        for (int j = 1; j < i; j++) {
            if (a[j] < a[i] && dp[j] + 1 > dp[i]) dp[i] = dp[j] + 1;
        }
    }
    cout << *max_element(dp + 1, dp + n + 1) << endl;  // 输出最大值
}

事实上,这个算法就能水过这道题

我们还有一种解法,大概思路就是将上面的方法中每次要重新遍历寻找的“比它大的元素中,已经组成的最长下降子序列的最大值”维护出来,具体看代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 100001;
int n, a[N];
int q[N];  // q[i]=a 表示长度为 i 的最长子序列的最后一个元素的值最小是 a
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    int len = 0;  // 最开始找到的最长子序列长度为 0
    q[0] = INT_MIN;
    for (int i = 1; i <= n; i++) {  // 遍历每个元素
        int l = 0, r = len;
        while (l < r) {
            // 二分寻找满足 q[j] < a[i] 的 j 的最大值
            int mid = (l + r + 1) >> 1;  // 玄学问题需要 +1,知道的话帮忙解释下
            if (a[i] > q[mid])
                l = mid;
            else
                r = mid - 1;
        }
        // q[r] < a[i],所以 r + 1 是新找到的最长子序列的长度
        // 又因为 q[r + 1] >= a[i],所以此处直接更新 q[r + 1]
        len = max(len, r + 1);
        q[r + 1] = a[i];
    }
    cout << len << endl;
}

这个思路看不懂的可以试试食用这篇文章:AcWing 896. 最长上升子序列 II - AcWing

这两个模板的测试数据:输入 10 -5 -1 8 -4 3 -2 -5 -6 2 -6 ,输出 4

最后是本题的 AC 代码:

#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
const int N = 5001;
int n, dp[N];
pii a[N];
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i].first >> a[i].second;
    sort(a + 1, a + n + 1);  // 默认 first 为第一关键字
    for (int i = 1; i <= n; i++) {
        dp[i] = 1;
        for (int j = 1; j < i; j++) {
            if (a[j].second > a[i].second && dp[j] + 1 > dp[i]) {
                dp[i] = dp[j] + 1;
            }
        }
    }
    cout << *max_element(dp + 1, dp + n + 1) << endl;
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

异想之旅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值