https://niumacode.com/training/121/problem/P1656

需要注意
在提取操作中检查优先级是否为最新,是为了解决“堆中存在旧版本数据”的问题,确保提取的是当前经验池中真实有效的高优先级经验。这一设计源于优先级队列(堆)的特性限制,具体原因如下:

include

include

include

include <unordered_map>

include <unordered_set>

include

include

include

using namespace std;

struct Experience {
int priority;
int id;

// 重载比较运算符,定义堆的排序规则
bool operator<(const Experience& other) const {
if (priority != other.priority) {
return priority < other.priority; // 优先级高的在前
}
return id > other.id; // ID小的在前
}
};

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

if (cin.fail() || N < 1 || N > 1e5) {
cout << "null" << endl;
return 0;
}

cin.ignore(); // 消耗掉换行符

vector operations;
string line;
for (int i = 0; i < N; ++i) {
if (!getline(cin, line)) {
cout << "null" << endl;
return 0;
}
operations.push_back(line);
}

// 如果还有剩余输入,说明操作数不匹配
if (getline(cin, line) && !line.empty()) {
cout << "null" << endl;
return 0;
}

priority_queue heap;
unordered_map<int, int> idToPriority; // 存储ID到优先级的映射
unordered_set extracted; // 记录已提取的ID
bool hasExtract = false;

for (const string& op : operations) {
stringstream ss(op);
char type;
ss >> type;

if (type == '+') { // 插入操作
int id, priority;
ss >> id >> priority;
if (ss.fail()) {
cout << "null" << endl;
return 0;
}
idToPriority[id] = priority;
heap.push({priority, id});
} else if (type == '-') { // 提取操作
hasExtract = true;
int K;
ss >> K;
if (ss.fail()) {
cout << "null" << endl;
return 0;
}

vector temp; // 临时存储弹出的元素
vector result; // 存储最终结果

// 从堆中找到K个未被提取的元素
while (!heap.empty() && result.size() < K) {
Experience e = heap.top();
heap.pop();
temp.push_back(e);

// 检查是否已被提取且优先级是否最新
if (extracted.find(e.id) == extracted.end() &&
e.priority == idToPriority[e.id]) {
result.push_back(e.id);
}
}

// 将临时存储的元素重新放回堆中
for (const auto& e : temp) {
heap.push(e);
}

// 输出结果
if (result.size() < K) {
cout << -1 << endl;
} else {
// 按优先级降序,ID升序排序
sort(result.begin(), result.end(), [&](int a, int b) {
if (idToPriority[a] != idToPriority[b]) {
return idToPriority[a] > idToPriority[b];
}
return a < b;
});

for (int i = 0; i < result.size(); ++i) {
cout << result[i];
if (i != result.size() - 1) {
cout << " ";
}
}
cout << endl;

// 标记这些ID已被提取
for (int id : result) {
extracted.insert(id);
}
}
} else if (type == '=') { // 更新操作
int id, newPriority;
ss >> id >> newPriority;
if (ss.fail()) {
cout << "null" << endl;
return 0;
}
idToPriority[id] = newPriority;
heap.push({newPriority, id});
extracted.clear(); // 重置提取标记
} else {
cout << "null" << endl;
return 0;
}
}

// 如果没有提取操作,输出null
if (!hasExtract) {
cout << "null" << endl;
}

return 0;
}

一、核心问题:堆无法高效更新旧元素

std::priority_queue(堆)是一种“静态”数据结构:

  • 当某个经验的优先级被更新(update 操作)时,堆中仍可能保留该经验的旧优先级版本(因为堆无法直接修改内部元素的值,也无法高效重新排序)。
  • 例如:经验 id=3 最初优先级为 7,被加入堆中;之后通过 update 将其优先级改为 20,堆中会新增一个 (20, 3) 的元素,但旧的 (7, 3) 元素仍在堆中。

此时,若提取时不检查优先级是否最新,可能会错误地提取到堆中残留的旧版本低优先级数据,导致结果错误。

二、具体场景示例

以用户提供的用例为例:

  1. 初始插入:(1,5)、(2,10)、(3,7),堆中元素为 (10,2)、(7,3)、(5,1)
  2. 第一次提取 K=2:正常提取 2(10)和 3(7),并标记为已提取。
  3. 更新操作:3 的优先级改为 20,堆中新增 (20,3),此时堆中存在旧版本 (7,3) 和新版本 (20,3)
  4. 第二次提取 K=1
    • 若不检查优先级是否最新,可能从堆中取出旧版本 (7,3)(但此时其真实优先级已更新为 20),导致错误。
    • 检查后会发现 (7,3) 的优先级与哈希表中记录的 20 不匹配,判定为旧版本,跳过该元素,转而提取新版本 (20,3)

三、本质:维护数据一致性

哈希表(idToPriority)存储的是当前最新的优先级,而堆中可能混合了新旧版本的优先级数据。提取时检查 e.priority == idToPriority[e.id],本质是:

  • 用哈希表作为“真理源”,验证堆中元素是否为当前有效的版本。
  • 过滤掉堆中残留的旧版本数据,确保提取的经验优先级是最新更新后的结果。

四、不检查会导致什么问题?

如果跳过优先级是否最新的检查,会出现两种错误:

  1. 提取到低优先级的旧版本:例如某经验已被更新为高优先级,但堆中旧版本的低优先级数据被错误提取,导致真实高优先级经验被遗漏。
  2. 优先级排序失真:堆的排序基于旧数据,提取结果的优先级顺序与实际最新优先级不符,违背“按当前优先级提取Top K”的需求。

总结

检查优先级是否最新,是为了解决堆无法高效更新旧元素的缺陷,通过哈希表与堆的“双重校验”,确保提取的是当前经验池中真实有效的高优先级数据。这一操作是维持数据一致性的关键,也是优先级经验回放机制正确性的保障。

比如在https://niumacode.com/problem/P1696中,需要用一个map或数组来记录有无遍历过,如果遍历过则在while循环中继续