Codeforces 1029B 创建比赛:题解|刷题打卡

161 阅读3分钟

本文正在参与掘金团队号上线活动,点击 查看大厂春招职位

一、题目描述:

今天我们来写一道 Codeforces 上的一道比较有意思的思维题:Codeforces 1029B 创建比赛

image.png

题目大意是给你一个升序数组 a1,...,ana_1,...,a_n,要你从里面找到一个子序列 b1,...,bmb_1,...,b_m 满足 i[i,m1]\forall i \in [i, m - 1] 都有 bi+12bib_{i+1} \leq 2b_i,并且使这个子集的大小最大。

二、思路分析:

乍一看这道题好像很难,在一个集合里面寻找满足条件的子集属于组合问题,而这道题可能的组合个数是 2n2^n 个,显然不能使用朴素的枚举组合来写,这个时候就需要动一点脑筋了。

如果一道题看起来很复杂而且还有解的话,那么它一定有化简的办法,这道题只给出了相邻元素的关系限制,我们可以以此为突破点找到规律。

  1. 我们不妨设 aia_i 是要选取的元素中最小的,那么 aia_i 之前的元素显然不满足条件,所以只需要考虑 aia_i 之后的元素,而且因为数组是有序的,后续我们只需要考虑 2ai\leq 2a_i 这个条件即可。

  2. 假设有两个元素满足条件,那么我们一定可以先将较小的那个加入,再加入较大的那个,因为 2ai\leq 2a_i 这个条件会随着加入元素的增大越来越宽松,这样贪心地找下去,我们就能找到以 aia_i 开始的满足条件的最大子序列。在这里我们也能够发现,用子串来描述这个结果集合会更适合,因为如果 ai+1a_{i+1} 都不满足条件,那后面的就更不可能了,所以不存在选择”断开“的可能。

  3. 一旦发现选择的子集只可能是子串,答案就呼之欲出了,因为选择不能”断开“,所以这些子串一定不会相邻,那么我们只要从头到尾找到所有满足条件的子串,然后输出最小的子串长度即可。

  4. 因为是顺序遍历,而且每个新加元素是否满足条件只有前一位有关,我们可以不用存储输入数组,而使用一个变量 pre 来记录上一位,然后循环更新答案即可。需要注意的是只选择一个元素总是满足条件的,所以我们要把 ans 初始化为 1

三、AC 代码:

#include <iostream>
using namespace std;

int main() {
    int n;
    cin >> n;

    int pre = 0;
    int ans = 1, cnt = 1;
    for (int i = 0; i < n; ++i) {
        int num;
        cin >> num;
        if (num <= pre * 2) {
            ++cnt;
            ans = max(ans, cnt);
        } else {
            cnt = 1;
        }
        pre = num;
    }

    cout << ans << endl;
}

四、总结:

这种”构造满足一定条件的子数组“的题是十分经典的一类题目,变体有很多,比如把上面的条件改成相邻元素小于一定值,做法还是一样的。所以遇到这种有套路的题目我们要及时归纳,做一道题就能做一类题,达到事半功倍的效果。