1 条题解
-
0
题意回顾
我们需要维护 个序列,支持:
- 在序列末尾插入/删除元素
- 合并两个序列(操作4,新建序列并删除原序列)
- 查询多个序列拼接后的绝对众数(出现次数 > 总长度一半)
数据范围:,总操作序列长度 。
核心思路
1. 绝对众数的性质
如果一个数 在序列中出现次数严格大于总长度的一半,那么它一定是绝对众数。
关键性质(摩尔投票法):
- 如果存在绝对众数,那么它一定是序列中任意一部分的众数候选者之一
- 我们可以用"对拼法"合并多个序列的候选者
具体来说:
- 维护一个候选值 和计数器
- 遍历元素:
- 如果 ,设 ,
- 否则如果 ,则
- 否则
- 最后 是可能的绝对众数候选,需要验证出现次数
2. 数据结构设计
由于需要支持快速合并(操作4),使用启发式合并。
对每个序列维护:
- 一个双向链表(或动态数组)存储元素
- 一个哈希表记录每个值的出现次数
- 序列长度
- 序列的摩尔投票信息(候选值和计数)
3. 操作实现
操作1:插入
- 在链表末尾插入元素
- 更新哈希表:
- 更新序列长度
- 更新摩尔投票信息(可选)
操作2:删除
- 从链表末尾删除元素
- 更新哈希表:
- 更新序列长度
操作4:合并
- 采用启发式合并:将较小序列的元素插入到较大序列中
- 时间复杂度:
- 总复杂度:
操作3:查询众数
-
候选阶段:
- 初始化 ,
- 遍历所有被拼接的序列,用摩尔投票法合并候选者
- 具体合并方法:
for 每个序列的候选值 c 和计数 t: if cnt == 0: cand = c, cnt = t else if cand == c: cnt += t else: cnt = abs(cnt - t) if cnt == 0: cand = -1
-
验证阶段:
- 如果 ,直接返回
- 否则统计 在所有拼接序列中的总出现次数
- 如果出现次数 ,返回 ,否则返回
复杂度分析
- 初始序列总长度
- 所有插入操作最多增加 个元素
- 启发式合并总复杂度:
- 查询操作总复杂度:,在数据范围内可接受
代码实现
#include <bits/stdc++.h> using namespace std; struct Sequence { list<int> elements; unordered_map<int, int> count; int len; int candidate; int candidate_cnt; Sequence() : len(0), candidate(-1), candidate_cnt(0) {} void update_candidate(int x) { if (candidate_cnt == 0) { candidate = x; candidate_cnt = 1; } else if (candidate == x) { candidate_cnt++; } else { candidate_cnt--; if (candidate_cnt == 0) candidate = -1; } } void push_back(int x) { elements.push_back(x); count[x]++; len++; update_candidate(x); } void pop_back() { if (elements.empty()) return; int x = elements.back(); elements.pop_back(); count[x]--; len--; // 需要重新计算候选者(简单实现:遍历) candidate = -1; candidate_cnt = 0; for (auto elem : elements) { update_candidate(elem); } } int get_count(int x) const { auto it = count.find(x); return it == count.end() ? 0 : it->second; } }; vector<Sequence> seqs; void merge_sequences(int x1, int x2, int x3) { Sequence& s1 = seqs[x1]; Sequence& s2 = seqs[x2]; // 启发式合并:总是合并到新的序列 Sequence new_seq; // 将s1的元素加入新序列 for (int elem : s1.elements) { new_seq.push_back(elem); } // 将s2的元素加入新序列 for (int elem : s2.elements) { new_seq.push_back(elem); } seqs[x3] = move(new_seq); } int query_sequences(const vector<int>& indices) { // 第一阶段:找候选者 int cand = -1, cnt = 0; for (int idx : indices) { const Sequence& s = seqs[idx]; if (s.candidate == -1) continue; if (cnt == 0) { cand = s.candidate; cnt = s.candidate_cnt; } else if (cand == s.candidate) { cnt += s.candidate_cnt; } else { cnt = abs(cnt - s.candidate_cnt); if (cnt == 0) cand = -1; } } if (cand == -1) return -1; // 第二阶段:验证 int total_len = 0; int total_count = 0; for (int idx : indices) { const Sequence& s = seqs[idx]; total_len += s.len; total_count += s.get_count(cand); } if (total_count * 2 > total_len) { return cand; } else { return -1; } } int main() { int n, q; cin >> n >> q; seqs.resize(n + q + 5); // 读入初始序列 for (int i = 1; i <= n; i++) { int len; cin >> len; for (int j = 0; j < len; j++) { int x; cin >> x; seqs[i].push_back(x); } } // 处理操作 for (int i = 0; i < q; i++) { int op; cin >> op; if (op == 1) { int x, y; cin >> x >> y; seqs[x].push_back(y); } else if (op == 2) { int x; cin >> x; seqs[x].pop_back(); } else if (op == 3) { int m; cin >> m; vector<int> indices(m); for (int j = 0; j < m; j++) { cin >> indices[j]; } cout << query_sequences(indices) << endl; } else if (op == 4) { int x1, x2, x3; cin >> x1 >> x2 >> x3; merge_sequences(x1, x2, x3); } } return 0; }
总结
本题的关键在于:
- 利用摩尔投票法的可合并性质处理多个序列的众数查询
- 采用启发式合并高效处理序列合并操作
- 分离候选和验证阶段保证正确性
- 合理选择数据结构平衡时间和空间复杂度
- 1
信息
- ID
- 5645
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 3
- 已通过
- 1
- 上传者