CF题解——Beppa and SwerChat

本文最后更新于 9 个月前,文中所描述的信息可能已发生改变。

H. Beppa and SwerChat 解题思路 ​

核心问题分析与数学转化 ​

本题要求计算在 9:00 和 22:00 之间,至少上线过一次的其他成员的最少人数。

设 AA 是 9:00 时成员的列表, BB 是 22:00 时成员的列表。列表按照最后一次上线时间递减排序。

  • 关键观察: 如果一个成员 XX 在 9:00 到 22:00 之间没有上线,那么他在 22:00 时的最后上线时间就是他在 9:00 时的最后上线时间(或者更早)。
  • 排序推论: 对于所有没有上线的成员构成的子集 UU,他们在列表 AA 中的相对顺序和在列表 BB 中的相对顺序必须是完全相同的。

因此,最少上线过的人数 MinOnline\text{MinOnline} 对应于 最多没有上线的人数 MaxOffline\text{MaxOffline}:

MinOnline=N−MaxOffline\text{MinOnline} = N - \text{MaxOffline}

我们的目标转化为:从 AA 和 BB 中找到能够保持相对顺序的最长公共子序列,这个子序列代表了 MaxOffline\text{MaxOffline} 的长度。

1. 相对顺序的转换与简化 ​

序列 AA 和 BB 都是 ID 列表。我们首先需要将 BB 列表转化为基于 AA 列表的排名或索引。

  1. 定义 AA 的排名: 在 9:00 的列表 AA 中,成员 aia_i 的排名定义为其在 AA 中的位置索引 i+1i+1 (即 11 到 NN)。

  2. 转换 BB 列表: 将 22:00 的列表 BB 中的每个 ID 替换为该 ID 在 AA 列表中的排名。设转换后的列表为 B′B'。

    • B′B' 的含义:列表中 Bj′=kB'_j = k 表示在 22:00 排在第 jj 位的成员,在 9:00 是排在第 kk 位的。
  3. 寻找 MaxOffline\text{MaxOffline}: 如果成员集合 UU 没有上线,那么:

    • 他们在 AA 中的顺序是递减的排名 k1>k2>…k_1 > k_2 > \dots。
    • 他们在 BB 中的顺序也必须是递减的排名 k1′>k2′>…k_1' > k_2' > \dots。

    由于我们已经将 BB 转换成了 AA 的排名 B′B',我们现在只需要在 B′B' 中寻找一个子序列,使得索引递增,且元素值递增(即 AA 的排名递增)。

    推论: 最多没有上线的人数 MaxOffline\text{MaxOffline} 等于 B′B' 列表(已转换成 AA 排名)中,从后向前找到的最长连续递增子序列的长度。

2. 动态规划思路 (等效于线性扫描) ​

在 B′B' 中,我们要找一个最长的后缀子序列 Bi′,Bi+1′,…,BN−1′B'_{i}, B'_{i+1}, \dots, B'_{N-1},满足 Bj′<Bj+1′B'_j < B'_{j+1}。

我们从 BN−2′B'_{N-2} 的倒数第二个元素开始向前扫描:

  • 初始化: 最长连续递增后缀长度 Length=1\text{Length} = 1 (包含 BN−1′B'_{N-1} 本身)。
  • 递推: 当我们遍历到索引 ii 时,如果 Bi′<Bi+1′B'_i < B'_{i+1},说明当前成员 Bi′B'_i 比后面的 Bi+1′B'_{i+1} 在 9:00 时排名靠前,且在 22:00 时也排在 Bi+1′B'_{i+1} 前面。这保持了相对顺序, Length←Length+1\text{Length} \leftarrow \text{Length} + 1。
  • 中断: 如果 Bi′≥Bi+1′B'_i \ge B'_{i+1},则相对顺序发生变化,说明 Bi′B'_i 或之前的某个成员一定上线过。我们在此中断,得到最大长度 MaxOffline=Length\text{MaxOffline} = \text{Length}。

最终答案:

MinOnline=N−Length\text{MinOnline} = N - \text{Length}。

CPP 代码实现 ​

代码中,我们首先使用 map 将 AA 列表的 ID 映射到其排名(1到N),然后用这些排名构建 B′B' 列表,最后通过线性扫描计算最长连续递增后缀的长度。

cpp
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

int calculate_max_offline(const vector<long long> &data_vector, int all_number) {
    if (all_number == 0) {
        return 0;
    }

    int length = 1;

    // 从倒数第二个元素开始向前扫描
    for (int i = all_number - 2; i >= 0; --i) {
        // 如果当前元素的 A 排名小于后面元素的 A 排名,则相对顺序未变
        if (data_vector[i] < data_vector[i + 1]) {
            length++;
        } else {
            // 相对顺序发生变化,中断
            break;
        }
    }

    // MaxOffline = length
    return all_number - length;
    
}

void solve() {

    int data_length;
    cin >> data_length;
    
    // 读取 A 列表
    vector<ll> a_list(data_length);
    // data_map: 存储 ID -> A 列表排名 (1-indexed)
    map<ll, ll> data_map;

    for (int i = 0; i < data_length; i++) {
        cin >> a_list[i];
        data_map[a_list[i]] = i + 1; // 排名从 1 开始
    }

    // 读取 B 列表,并将其原地转换为 B' (A 排名列表)
    vector<ll> b_prime_list(data_length);
    for (int i = 0; i < data_length; i++) {
        int id;
        cin >> id;
        b_prime_list[i] = data_map[id]; // 将 ID 替换为 A 排名
    }

    // 计算最少上线人数 = 总人数 - 最多没上线人数 (MaxOffline)
    cout << calculate_max_offline(b_prime_list, data_length) << '\n';

}

int main() {

    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);

    int t;
    cin >> t;
    while (t--) {
        solve();
    }

}
CF题解——Garage
CF题解——Block Sequence