1 条题解
-
0
题目大意
有 个学生,每人有一个不同的运动能力值 (1 到 )。学生按顺序排成一列,老师从左到右依次评分,分数为 1 到 的不同整数。
评分需要满足两个条件:
- 能力约束:能力强的学生分数不能低于能力弱的学生
- 顺序约束:右边的学生分数不能低于左边的学生
在满足条件的前提下,老师希望使用尽可能多的不同分数。
每节课后会有两个学生交换位置,需要求出每节课评分时最多能使用的不同分数种类。
解题思路
问题分析
这个问题可以转化为:给定一个排列,找到满足以下条件的最长序列的长度:
- 序列在原始排列中是递增的(顺序约束)
- 序列对应的能力值也是递增的(能力约束)
换句话说,我们需要找到排列和能力值序列的最长公共递增子序列。
关键观察
设 表示能力值为 的学生在队列中的位置。
条件可以重新解释为:
- 如果我们选择一个分数序列 ,那么:
- (顺序约束)
- (能力约束)
因此,问题转化为:在能力值序列上,找到最长的递增子序列,使得这些能力值对应的位置也是递增的。
动态规划解法
设 表示以能力值 结尾的最长合法序列长度。
转移方程:
$$dp[i] = 1 + \max\{dp[j] \mid j < i \text{ 且 } pos[j] < pos[i]\} $$这个 DP 可以在 时间内计算,但对于 来说太慢。
数据结构优化
我们需要维护一个数据结构,支持:
- 查询所有 且 的 的最大值
- 插入 到位置
这可以用树状数组(Fenwick Tree)或线段树来实现。
具体步骤:
- 按能力值从小到大处理每个学生
- 对于能力值 ,查询位置在 之前的所有 值的最大值
- 在位置 更新值为
时间复杂度:
处理交换操作
当两个学生交换位置时,我们需要动态维护这个 DP 值。
交换位置 和 的学生:
- 设这两个学生的能力值分别为 和
- 交换 和
- 重新计算所有受影响的 值
但是直接重新计算所有受影响的 值在最坏情况下是 的,对于 来说不可行。
优化策略
观察 1:答案的单调性
交换操作通常只会影响局部的 值,整体答案变化不大。
观察 2:只更新必要的值
当交换 和 时,只有能力值在 和 之间的学生可能受到影响。
具体来说,需要重新计算:
- 能力值在 范围内的学生
- 这些学生的 值可能依赖于交换的两个位置
高效实现
- 使用平衡树或线段树维护每个位置的 值
- 交换时,只重新计算受影响区间内的 值
- 使用懒惰传播或增量更新来优化
代码框架
#include <iostream> #include <vector> #include <algorithm> using namespace std; const int MAXN = 1000005; int n, z; int a[MAXN], pos[MAXN], dp[MAXN]; // 树状数组用于查询前缀最大值 struct Fenwick { vector<int> tree; int size; Fenwick(int n) : size(n), tree(n + 1, 0) {} void update(int idx, int val) { while (idx <= size) { tree[idx] = max(tree[idx], val); idx += idx & -idx; } } int query(int idx) { int res = 0; while (idx > 0) { res = max(res, tree[idx]); idx -= idx & -idx; } return res; } }; int solve() { Fenwick bit(n); int ans = 0; // 按能力值从小到大处理 for (int i = 1; i <= n; i++) { dp[i] = bit.query(pos[i]) + 1; bit.update(pos[i], dp[i]); ans = max(ans, dp[i]); } return ans; } void swap_students(int p, int q) { // 交换位置p和q的学生 int x = a[p], y = a[q]; swap(a[p], a[q]); swap(pos[x], pos[y]); } int main() { ios::sync_with_stdio(false); cin.tie(0); cin >> n >> z; for (int i = 1; i <= n; i++) { cin >> a[i]; pos[a[i]] = i; } // 输出第一节课的答案 cout << solve() << "\n"; for (int i = 1; i < z; i++) { int p, q; cin >> p >> q; swap_students(p, q); // 这里需要优化:只重新计算受影响的部分 // 简化版本:直接调用solve()(对于小数据) cout << solve() << "\n"; } return 0; }进一步优化
对于大数据(),需要更精细的实现:
- 局部更新:只重新计算受交换影响的学生
- 批量处理:将多个交换操作一起处理
- 分摊复杂度:使用更高效的数据结构
算法标签
- 动态规划
- 树状数组/线段树
- 最长递增子序列
- 数据结构优化
- 交换操作处理
复杂度分析
- 初始解法:
- 每次交换:最优可达到 或
- 总复杂度:
这个问题的难点在于高效处理动态的交换操作,需要巧妙的数据结构设计和局部更新策略。
- 1
信息
- ID
- 4218
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 24
- 已通过
- 1
- 上传者