1 条题解
-
0
题解
题目类型:可持久化线段树(主席树)
核心任务:多次区间查询某个“出现次数超过一半”的元素(即区间众数判定)。
一、题意概述
给定一个长度为 ( n ) 的数列 ( a_1, a_2, \dots, a_n ),以及 ( m ) 次查询,每次给出一段区间 ([l,r])。
对于每个查询,需要判断该区间内是否存在某个数出现次数超过一半(即 ( >\frac{r-l+1}{2} )),
若存在,则输出该数;若不存在,则输出 0。
二、算法思路:可持久化线段树(主席树)
1️⃣ 可持久化线段树的作用
主席树是一种 记录前缀信息的持久化数据结构,可在 (O(\log n)) 时间内实现如下操作:
- 快速查询区间 ([l,r]) 中某个数值出现次数;
- 通过根节点差
rt[r] - rt[l-1]
获取“差分区间”数据; - 每个节点仅在插入时新建一小部分(log 层),总复杂度 (O(n \log n))。
在本题中,我们将值域设为 ([1,n]),每个位置
i
插入一个值a[i]
,维护前缀出现次数。
2️⃣ 主席树节点定义
struct tree { int ls, rs; // 左右子节点编号 int cnt; // 当前区间内的计数 } tr[N * 20];
- 每个版本的根节点编号保存在
rt[i]
中; rt[i]
表示序列前缀[1, i]
的统计信息;new_node()
用于复制上一版本节点(持久化核心);modify()
用于在对应值x
上 +1,构建新的版本。
3️⃣ 构建过程
for (int i = 1; i <= n; i++) { int x; scanf("%d", &x); rt[i] = rt[i - 1]; // 从上一个版本拷贝 modify(rt[i], 1, n, x, 1); // 在位置 x 上出现一次 }
- 第 i 次插入得到第 i 个版本;
- 每个版本与前一版本仅有 (O(\log n)) 个新节点;
- 因此总空间复杂度 (O(n \log n))。
4️⃣ 查询逻辑:查找是否存在“过半数元素”
函数:
int query(int id1, int id2, int l, int r, int k)
其中:
id1
是前缀版本rt[l-1]
id2
是前缀版本rt[r]
- 通过
(tr[id2].cnt - tr[id1].cnt)
获得区间[l, r]
的频次
判断条件:
if ((tr[id2].cnt - tr[id1].cnt) * 2 <= k) return 0;
表示当前区间总频次不足半数(没有超过一半的候选),返回 0。
若当前节点为叶子(
l == r
),即找到了出现次数最多的候选值。否则:
- 先查看左子树中该元素数量是否超过半数;
- 若是,则继续往左;
- 否则往右找。
int mid = (l + r) / 2; int x = tr[tr[id2].ls].cnt - tr[tr[id1].ls].cnt; if (x * 2 > k) return query(tr[id1].ls, tr[id2].ls, l, mid, k); return query(tr[id1].rs, tr[id2].rs, mid + 1, r, k);
这就相当于在二分值域上查找出现次数超过一半的那个数。
三、算法正确性说明
- 主席树记录了从前缀 1~r 的出现频率;
- 差分
rt[r] - rt[l-1]
恰好代表区间[l,r]
; - 查询时不断往下递归,判断左右区间内是否存在出现次数超过半数的值;
- 若出现次数都 ≤ 半数,则返回 0;
- 否则定位到该具体数值。
因此每次查询能在 (O(\log n)) 时间内得到答案。
四、复杂度分析
操作 复杂度 建树 (O(n \log n)) 单次查询 (O(\log n)) 总复杂度 (O((n+m)\log n)) 空间复杂度 (O(n \log n))
五、代码解释关键点
if ((tr[id2].cnt - tr[id1].cnt) * 2 <= k) return 0; // 当前区间无过半元素 if (l == r) return l; // 找到候选数值
k = r - l + 1
:当前区间长度;2 * cnt <= k
:即 cnt ≤ floor(k/2),没有过半数;- 否则继续往下查找更具体的区间。
六、总结
- 技术要点:主席树的“版本差”思想;
- 核心判断:出现次数 ×2 是否超过区间长度;
- 应用场景:区间众数、区间多数元素、频率统计问题。
七、完整代码(与题目一致)
#include <bits/stdc++.h> using namespace std; namespace Cherry { const int N=5e5+5; int n,m,cnt; int rt[N]; struct tree { int ls,rs,cnt; }tr[N*20]; int new_node(int x) { return tr[++cnt]=tr[x],cnt; } void pushup(int id) { tr[id].cnt=tr[tr[id].ls].cnt+tr[tr[id].rs].cnt; } void modify(int &id,int l,int r,int x,int d) { id=new_node(id); if(l>=x&&r<=x) return tr[id].cnt+=d,void(); int mid=(l+r)/2; if(x<=mid) modify(tr[id].ls,l,mid,x,d); else modify(tr[id].rs,mid+1,r,x,d); pushup(id); } int query(int id1,int id2,int l,int r,int k) { if((tr[id2].cnt-tr[id1].cnt)*2<=k) return 0; if(l==r) return l; int mid=(l+r)/2; int x=tr[tr[id2].ls].cnt-tr[tr[id1].ls].cnt; if(x*2>k) return query(tr[id1].ls,tr[id2].ls,l,mid,k); return query(tr[id1].rs,tr[id2].rs,mid+1,r,k); } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { int x; scanf("%d",&x); rt[i]=rt[i-1]; modify(rt[i],1,n,x,1); } for(int i=1;i<=m;i++) { int l,r; scanf("%d%d",&l,&r); printf("%d\n",query(rt[l-1],rt[r],1,n,r-l+1)); } return 0; } } int main() { Cherry::main(); return 0; }
- 1
信息
- ID
- 3558
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者