区间摩尔投票 - 教程

news/2025/10/24 10:23:40/文章来源:https://www.cnblogs.com/lxjshuju/p/19162534

摩尔投票

寻找绝对众数

给定一个长度为 nnn 的数组 aaa,已知数组 aaa 内有一种数出现次数比其他数出现次数之和还要大,请求出这个数。

假如存在一个数字,出现次数比其他所有的数出现次数之和还要大。那么我们可以不断地选择 两个不同的数 并将这两个数删去,直到数组中只剩下一种数。

此时剩下的数就是答案

这是因为对于真正的答案 xxx,即使考虑最坏的情况,即每次选择一个 xxx 和非 xxx 的数,由于 xxx 的出现次数大于其他所有数的出现次数之和,故最终剩下的仍是 xxx

可以用两个变量 numcnt 分别记录 当前最有可能成为答案的数 以及 这个数目前的优势

不妨假设 numcnt 表示的是 1∼i−11\sim i-11i1 内的信息,那么要转移到 1∼i1\sim i1i,则需要

  • 判断是否 numa[i] 相同,若相同则 cnt 增加 111,表示优势增加 111,反正 cnt 减少 111 表示优势减少 111
  • cnt 是负数,说明 num 相对于其他数而言不再具备优势,此时我们将 num 换成新加进来的数 a[i],并令 cnt111

根据上面的结论,不难得出 局部最优最终会发展为全局最优

#include <bits/stdc++.h>using namespace std;//#pragma GCC optimize(2)#define int long long#define endl '\n'#define PII pair<int,int>#define INF 1e18void slove () {int n;cin >> n;int num = INF, cnt = 0;for (int i = 1; i <= n; i++) {int a;cin >> a;if (num == a) cnt ++;else {if (cnt == 0) {num = a;cnt = 1;} else {cnt --;}}}cout << num << endl;}signed main () {ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);slove();}

寻找出现次数大于 n/k 的所有数

给定一个长度为 nnn 的数组 aaa,请找出 aaa 内出现次数大于 nk\frac{n}{k}kn 的所有数,kkk 至多不超过 101010

一共 nnn 个数,出现次数大于 nk\frac{n}{k}kn 的数显然不超过 k−1k-1k1 个,可以参考摩尔投票,每次选 kkk 不同的个数删去,直到数组内只剩下少于 kkk 种数,则剩下的数 可能是 我们要找的出现次数大于 nk\frac{n}{k}kn 的数。

这里要注意我的用词,可能是,因为我们只能证明出现次数大于 nk\frac{n}{k}kn 的数必然会留下来,但是不能推出留下来的就一定是出现次数大于 nk\frac{n}{k}kn 的数。

证明很简单,假设存在 xxx 种出现次数大于 nk\frac{n}{k}kn 的数,显然除此之外的所有数的出现次数均不大于 nk\frac{n}{k}kn,而最坏的情况是我们令这 xxx 种数都包含在每次选择的 kkk 种数中,由于不超过 k−1k-1k1 个数的出现次数大于 nk\frac{n}{k}kn,所以这 xxx 种数至多减少 nk\frac{n}{k}kn,也就是最少会剩下 111

我们可以维护一个大小不超过 k−1k-1k1 的集合 AAA,集合 AAA 内的每个元素是一组 (num, cnt),表示当前数 num 目前的优势 cnt

不妨假设集合 AAA 内的元素表示的是 1∼i−11\sim i-11i1 内最有可能成为答案的数的集合,那么要转移到 1∼i1\sim i1i,则

最终集合 AAA 内剩下来的元素均可能作为答案,所以我们要对它们进行最后一次检查,即遍历数组检查它们的出现次数是否大于 nk\frac{n}{k}kn

这样我们就不需要去管其他的非候选数的出现次数,需要被专门记录的数从原来的 nnn 变成了 k−1k-1k1

#include <bits/stdc++.h>using namespace std;//#pragma GCC optimize(2)#define int long long#define endl '\n'#define PII pair<int,int>#define INF 1e18void slove () {int n, k;cin >> n >> k;map <int, int> mp;for (int i = 1; i <= n; i++) {int x;cin >> x;if (mp.count(x)) mp[x] ++;else {if (mp.size() < k - 1) {mp[x] = 1;} else {vector <int> del;for (auto &j : mp) {j.second --;if (j.second == 0) del.push_back(j.first);}for (auto j : del) mp.erase(j);}}}}signed main () {ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);slove();}

摩尔投票的结合性

在摩尔投票与线段树结合起来之前,先证明一下 摩尔投票的结合性

假设我们要求区间 [1,n][1,n][1,n] 内的出现次数大于 n2\frac{n}{2}2n 的数。

那么我是否可以先将 [1,n][1,n][1,n] 分成若干个更小的互不重叠的区间,分别计算答案后结合起来变成最终答案呢?

显然是可以的,因为我们可以把 拆分为若干个区间 看做对这些区间内的数进行“每次选择两个不同的数删去”的操作,那么区间剩下来的一个数,就表示这个区间内最有可能成为答案的数,即 候选数

由摩尔投票可知,若存在出现次数超过 n/2n/2n/2 的数字,则不管按照什么顺序进行操作,最终得到的答案必然是唯一的,所以这若干个互不重叠的小区间合并后得到的候选数就一定是最终的答案。

所以我们可以得出一个结论,摩尔投票是具有结合性的

具体怎么结合呢,下面给出一个简单的例子:

[1,n/2][1,n/2][1,n/2] 所得到的候选数是 x1x_1x1,其优势是 cnt1cnt_1cnt1[n/2+1,n][n/2+1,n][n/2+1,n] 所得到的候选数是 x2x_2x2,其优势是 cnt2cnt_2cnt2

  • x1=x2x_1=x_2x1=x2,则最终的候选数是 x1x_1x1
  • x1≠x2x_1\neq x_2x1=x2,则最终的候选数是 cntcntcnt 值更大的。

不妨设 cnt1>cnt2cnt_1>cnt_2cnt1>cnt2,则最终 [1,n][1,n][1,n] 内的答案是 x1x_1x1,其优势是 cnt1−cnt2cnt_1-cnt_2cnt1cnt2

求区间[l,r]内出现次数大于 (r-l+1)/k 的所有数

这是摩尔投票的最终版本,从原来的求全局出现次数大于 n2\frac{n}{2}2n 的数,到求区间 [l,r][l, r][l,r] 出现次数大于 r−l+1k\frac{r-l+1}{k}krl+1 的数,如果你没有掌握 全局出现次数大于 nk\frac{n}{k}kn 的数摩尔投票的结合性,不要看下面内容。

如何求解求区间 [l,r][l, r][l,r] 出现次数大于 r−l+1k\frac{r-l+1}{k}krl+1 的所有数?

缩减目标范围

区间 [l,r][l, r][l,r] 出现次数大于 r−l+1k\frac{r-l+1}{k}krl+1 的数不会超过 k−1k-1k1 个。

故我们可以用摩尔投票来忽略那些不可能成为候选数的数字进而减少时间复杂度。

因为一般 kkk 都不会给很大,所以就变成了对区间的 k−1k-1k1 个数字进行检查,检查它们出现次数是否大于 nk\frac{n}{k}kn

因为摩尔投票具有结合性,故我们可以 预处理若干个区间信息(当前区间候选数及其优势),然后对于每次要查询的区间,用 若干个区间信息拼凑成目标区间

根据上面所说的需求,我们可以使用 线段树 来维护,线段树的每个结点都表示一个区间,结点信息应该是若干个候选数及其优势,也就是若干个二元组。

当要进行合并区间的时候,我们先将所有的候选数及其优势合并到一个集合,如果候选数不超过 k−1k - 1k1 个,那么父亲结点所代表区间的候选数集合就是当前这个集合,直接上传即可。

如果候选数超过了 k−1k-1k1 个,那么选择优势最小的候选数删去,并将其他的候选数的优势减少同等数值,直到候选数不超过 k−1k-1k1 个。

我们维护一个 vector<PII> tmp,把左右儿子的候选放入里面,再维护答案 vector<PII> candidates 数组,遍历 tmp 内所有候选元素,对于当前候选元素 candidate, val

  • 如果它没在 candidates 内出现,那么直接加入 candidates 数组内。
  • 如果它在 candidates 内出现,那么把 val 值更新一下。

candidates 内的元素超过 k−1k-1k1 个,我们就遍历找到 val 值最小的元素,将其删去,然后再把其他元素的优势都减去 val,如果优势为 000 则也需要被删去。

值得注意的是,由于 kkk 较小,所以我们尽量用暴力遍历和 vector 来代替其他的带 log 的数据结构。

检查候选目标

当我们从线段树中获得了目标区间的候选数集合时,仍需要检查这些候选数的合规性,即出现次数大于 r−l+12\frac{r-l+1}{2}2rl+1

对于一个区间 [l,r][l,r][l,r] 内的数(且这个数并不能预处理,具有一定的普遍性),如何检查其出现次数?

可以对每个数 xxx 开一个 vector,里面 xxx 的所有可能出现的位置,并将其排好序。

然后对这个 vector 进行二分有多少位置出现在 [l,r][l,r][l,r] 之间即可,由于 kkk 很小,所以我们每次二分的时间复杂度至多 O(logn)O(logn)O(logn)

而线段树的时间复杂度也是 lognlognlogn,故总时间复杂度会是 O(qlogn)O(qlogn)O(qlogn)

最终代码

例题

https://codeforces.com/contest/2149/problem/G

#include <bits/stdc++.h>using namespace std;//#pragma GCC optimize(2)#define int long long#define endl '\n'#define PII pair<int,int>#define INF 1e18struct SegmentTreeNode {vector <PII> candidates; // 候选人SegmentTreeNode() {}};struct SegmentTree {vector <SegmentTreeNode> tr;int n, k;SegmentTree(int size, int _k) : n(size), tr(size << 2), k(_k) {}SegmentTreeNode merge(SegmentTreeNode a, SegmentTreeNode b) {SegmentTreeNode now;vector<PII> tmp = a.candidates;for (auto x : b.candidates) tmp.push_back(x);// 合并所有候选for (auto [cnt, val] : tmp) {bool found = false;for (auto &it : now.candidates) {// 当前候选人在之前出现过,那么直接累加if (it.second == val) {it.first += cnt;found = true;break;}}// 如果当前if (!found) now.candidates.push_back({cnt, val});if (now.candidates.size() > k - 1) {// 抵消最小次数int mn = now.candidates[0].first;for (auto &it : now.candidates) mn = min(mn, it.first);for (auto &it : now.candidates) it.first -= mn;vector<PII> t;for (auto &it : now.candidates) if (it.first > 0) t.push_back(it);now.candidates = t;}}return now;}void pushup(int node) {tr[node] = merge(tr[node << 1], tr[node << 1 | 1]);}void build(int node, int l, int r, const vector<int>& a) {if (l == r) {tr[node].candidates.push_back({1, a[l]});return;}int mid = (l + r) >> 1;build(node << 1, l, mid, a);build(node << 1 | 1, mid + 1, r, a);pushup(node);}SegmentTreeNode query (int node, int L, int R, int l, int r) {if (l > r || l > R || r < L) return SegmentTreeNode(); // 区间非法//opt1:区间覆盖if (l <= L && r >= R) {return tr[node]; // 返回该结点信息。}int mid = (L + R) >> 1;SegmentTreeNode q1 = query (node << 1, L, mid, l, r);SegmentTreeNode q2 = query (node << 1 | 1, mid + 1, R, l, r);return merge(q1, q2); // 返回合并起来的信息,不一定是求和}};void slove () {int n, q;cin >> n >> q;SegmentTree tt(n, 3);vector <int> a(n + 1);map <int, vector<int>> mp;for (int i = 1; i <= n; i++) {cin >> a[i];mp[a[i]].push_back(i);}tt.build(1, 1, n, a);while (q--) {int l, r;cin >> l >> r;SegmentTreeNode now = tt.query(1, 1, n, l, r);sort (now.candidates.begin(), now.candidates.end(), [](PII A, PII B) {return A.second < B.second;});bool is_ok = 0;for (auto [val, candidate] : now.candidates) {int L = lower_bound(mp[candidate].begin(), mp[candidate].end(), l) - mp[candidate].begin();int R = lower_bound(mp[candidate].begin(), mp[candidate].end(), r + 1) - mp[candidate].begin();if ((R - L) > (r - l + 1)/3) {cout << candidate << ' ';is_ok = 1;}}if (is_ok == 0) cout << -1;cout << endl;}}signed main () {ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);int T;cin >> T;while (T--) slove();}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/944990.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一张图讲清楚企业微信的好友和群

适用企业微信版本:2025年10月24日前后。企业微信一直在改版,官方文档大部分滞后,参考性不强,主要还得靠自己试。

中国企业DevOps工具链选型:本土化适配与安全可控成关键考量

中国企业DevOps工具链选型:本土化适配与安全可控成关键考量 随着中国企业数字化转型进入深水区,DevOps工具链的选择已成为企业技术战略的重要组成部分。在云原生、安全合规等核心需求驱动下,国内企业正面临阿里云效…

技术拐点将至:AI 大模型的挑战突围与产业重构 - 指南

技术拐点将至:AI 大模型的挑战突围与产业重构 - 指南pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&qu…

详细介绍:如何将华为手机的照片转移到电脑

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

Executing System Commands in Python - ENGINEER

Executing System Commands in Python Executing system commands in Python can be done using the os and subprocess modules. These modules allow you to run shell commands from within your Python code, whi…

【读论文】AI笔记(一)9月26日组会前 - 教程

【读论文】AI笔记(一)9月26日组会前 - 教程2025-10-24 10:18 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: b…

2025中国DevOps平台选型全景洞察:本土化与安全可控成关键考量

2025中国DevOps平台选型全景洞察:本土化与安全可控成关键考量 随着中国企业数字化转型进入深水区,DevOps平台选型正面临前所未有的复杂局面。在技术自主可控与全球化协作的双重需求下,国内企业需要重新审视DevOps工…

增强AI股票预测分析报告 - 2025年10月24日 - 10:18:59

增强AI股票预测分析报告 - 2025年10月24日body { font-family: "Microsoft YaHei", "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; color: rgba(51, 51, 51, 1); max-widt…

容器主机名解析在香港服务器内部网络的调试方案 - 教程

容器主机名解析在香港服务器内部网络的调试方案 - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&q…

win10开始安装vs2022时闪退问题记录

原因:win10系统版本过低系统版本需 win10 1909以上 解决方法: Step 1:如果电脑联网,直接在线更新系统;如果电脑没有网络,则需要去官网下载升级包进行升级 下载 Windows 10 Step2:(离线升级系统时需要)离线升级系统…

领取快手的3个月的 KAT-Coder-Pro V1 编程 Tokens 资源包

这是快手的 AI Coding 大模型产品 https://www.streamlake.ai/product/kat-coder 国内访问,https://www.streamlake.com/product/kat-coder 通过国外IP,谷歌账号,过安全认证,登录,领取 3 个月有效的 20 M tokens的…

(WebSocket)心理咨询管理系统开发ing......

在昨天进行开发心理咨询管理系统过程中,到了咨询师和客户进行消息发送的阶段, 在这个过程中,我设计是一个Message表,里面的字段包括日常id以及发送人和接 收者的id我们通过发送消息将数据存入这个表中,在发送消息…

NACOS 2.4.1 数据库表详解

-----------------------------------------------------------------------------------------NACOS 2.4.1 数据库表详解 在 Nacos 2.4.1 版本中,数据库表结构主要分为 配置管理、服务注册与发现、权限控制、集群管理…

2025 年硅砂模块实力厂家最新推荐排行榜:涵盖新型 / 第三代承插型等多类型产品,多维度解析优质企业优势

引言 当前海绵城市建设进程持续加快,硅砂模块作为雨水收集利用系统的核心组件,市场需求大幅增长。但市场乱象凸显,不仅厂家数量繁杂,产品质量更是差距悬殊,部分企业用劣质原料生产,导致模块抗压性弱、易腐蚀,直…

1基础的UActorComponent基类实现功能模块化

1基础的UActorComponent基类实现功能模块化组件模式(Component Pattern)​​​​实现方式​​:通过 UActorComponent基类实现功能模块化,例如 USceneComponent(场景组件)、UStaticMeshComponent(静态网格组件)…

2025 泳池设备厂家专业解决方案与设备优势,推荐 Firsle 法思乐,全产业链服务解析

行业背景 随着人们生活品质提升与文旅产业蓬勃发展,泳池与海洋馆建设需求持续增长,从私家别墅、高端民宿到大型商业综合体、文旅项目,对水质安全、设备稳定性及系统智能化的要求日益严苛。传统泳池设备存在安装复杂…

2025年10月杭州模拟人开发公司对比榜:服务链路深度拆解

正在找“杭州模拟人开发公司”的你,大概率面临三类场景:一是医学院或急救中心需要高仿真急救模拟人升级考核系统,预算充足却担心售后响应;二是科技初创团队想快速拿到可二次开发的数字人SDK,对交付周期敏感;三是…

医用制氧机哪家好?2025医用制氧机厂家权威排行榜

医用制氧机是保障医疗供氧的关键设备,需符合严格的国家与国际标准,核心指标包括氧浓度≥90%、流量稳定、运行安全且耐用。市面上有许多技术扎实、专注细分领域的实力厂家,它们的产品在性能、适配场景等方面各有优势…

NativeMessaging通信失败问题

最近遇到了一个奇怪的问题:NativeMessaging消息发出去,但插件没有执行任何操作。 后面排查后发现,发送的消息为JSON文件,文件超过1024KB: chrome要求,发送的消息最多不超过1MB,传回的消息不超过64MB,压缩代码,…

2025年10月自行车品牌评价榜:十款热门车型数据对比

国庆长假刚过,城市通勤、亲子郊游、周末骑行的需求集中释放,很多人第一次认真考虑“买一辆真正适合自己的自行车”。他们可能是早上七点被地铁挤到变形的白领,也可能是想陪孩子学会骑车的年轻父母,或是把骑行写进退…