题目内容
有几个序列(依次编号为 0 , 1 , . . . . , n − 1 0,1,....,n - 1 0,1,....,n−1 ),初始时各个序列都为空。你的任务是维护这 n n n 个序列,需要进行的各种操作的表示与意义如下:
- 1 i k x 1\ i\ k\ x 1 i k x :在第 i i i 个序列的末尾插入 k k k个值都为 x x x 的数;
- 2 i k 2\ i\ k 2 i k :删除第 i i i 个序列末尾的 k k k 个数,若该序列已不足 k k k 个数,则删除序列中全部的数;
- 3 i 3\ i 3 i :询问第 i i i 个序列的众数。
其中,一个序列的众数定义为该数列中出现次数最多的数,若出现次数最多的数有多种,取其中数值最小的数。
输入描述
输入第一行为两个正整数 n , q n,q n,q ,表示序列的个数与操作次数。
接下来 q q q 行描述依次进行的操作,每行描述一个操作,每个操作的输入方式同题目描述。
对于所有输入数据,均满足 1 ≤ n ≤ 1 0 5 , 1 ≤ q ≤ 1 0 6 1\le n\le 10^5,1\le q\le 10^6 1≤n≤105,1≤q≤106 ,任何出现的序列编号 i i i 都满足 0 ≤ i < n 0\le i\lt n 0≤i<n 。
序列中出现的任何数值 x x x 均满足 0 ≤ x ≤ 1 0 9 0\le x\le 10^9 0≤x≤109 ,插入和删除操作中的数目 k k k 满足 1 ≤ k ≤ 1 0 9 1\le k \le 10^9 1≤k≤109 。
输出描述
对于每个询问操作, 输出询问时对应序列中出现次数最多的数中数值最小者,并换行。
样例
输入
2 6
1 0 2 1
1 0 3 2
3 0
2 0 1
3 0
3 1
输出
2
1
-1
样例解释
第
1
1
1 次询问时,
0
0
0 号序列为 1 1 2 2 2
,唯一众数为
2
2
2 。
第
2
2
2 次淘问时,
0
0
0 号序列为 1 1 2 2
,两种数都出现了
2
2
2 次,取较小的
1
1
1 。
第 3 3 3 次询问时, 1 1 1 号序列为空,输出 − 1 -1 −1 。
题目思路
分析
数据结构题:值域线段树 + 离线处理。
1.离线:观察到队列之间的操作是独立的。所以可以先把所有答案先全部读入进来。然后分队列处理
2.维护队列:
1.由于需要维护尾部删除和尾部增加的操作,所以自然选择栈来维护这样的关系。由于 k k k比较大,栈中存储二元组 ( x , k ) (x , k) (x,k)代表 k k k个 x x x
2.由于需要求解序列上的众数。所以需要一种数据结构来维护每个数出现的次数以及"次数"们的最大值。可以使用值域线段树或者使用 h a s h hash hash表 配合 红黑树 来维护这个东西。后者直接使用语言库自带的对象可能常数会比较大。
复杂度
O ( q l o g q ) O(q\ log\ q) O(q log q)
证明
假设队列 i i i上的操作为 q i q_i qi次 ,那么操作次数为 q i l o g q i + q i q_ilog\ q_i + q_i qilog qi+qi
前者是权值线段树的开销。后者是栈的开销。
总操作次数为:
∑
i
=
1
n
q
i
l
o
g
q
i
,
s
.
t
.
∑
i
=
1
n
q
i
=
q
\sum_{i=1}^{n}q_i\ log\ q_i , s.t.\sum_{i=1}^{n} q_i=q
i=1∑nqi log qi,s.t.i=1∑nqi=q
最差情况:
{
m
a
x
f
(
n
,
q
)
=
∑
i
=
1
n
q
i
l
o
g
q
i
s
.
t
.
∑
i
=
1
n
q
i
=
q
\left\{\begin{matrix} max\ f(n,q)=\sum_{i=1}^{n}q_i\ log\ q_i \\ s.t. \sum_{i=1}^{n} q_i=q \end{matrix}\right.
{max f(n,q)=∑i=1nqi log qis.t.∑i=1nqi=q
根据拉格朗日定理,当
q
1
=
q
2
=
.
.
.
=
q
n
q_1=q_2=...=q_n
q1=q2=...=qn时,目标函数最大化。那么
f
m
a
x
(
n
,
q
)
=
n
∗
q
n
l
o
g
q
n
=
q
(
l
o
g
q
−
l
o
g
n
)
=
O
(
q
l
o
g
q
)
f_{max}(n , q) =n * \frac{q}{n}\ log\ \frac{q}{n}=q(log\ q-log\ n) = O(q\ log\ q)
fmax(n,q)=n∗nq log nq=q(log q−log n)=O(q log q)
代码+解析
C++
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
const int maxq = 1e6 + 6;
#define ll long long
// 线段树部分 开始
struct Node {
int id , op , k , x;
};
vector<Node> op[maxn];
int ans[maxq];
int dist[maxq] , cnt;
struct TreeNode {
int l , r;
ll num;
}st[maxq << 4];
void build (int l , int r , int t){
if (l > r) return ;
st[t].l = l;
st[t].r = r;
st[t].num = 0;
if (l == r) return ;
int mid = l + r >> 1;
int ls = t << 1 , rs = ls + 1;
build(l , mid , ls);
build(mid + 1 , r , rs);
}
void pushup (int t){
int ls = t << 1 , rs = ls + 1;
st[t].num = max(st[ls].num , st[rs].num);
}
void add (int t , int pos , ll x){
if (st[t].l == st[t].r){
st[t].num += x;
return ;
}
int mid = st[t].l + st[t].r >> 1;
int ls = t << 1 , rs = ls + 1;
if (pos <= mid) add(ls , pos , x);
else add(rs , pos , x);
pushup(t);
}
// 寻找众数
int findmx (int t){
if (st[t].l == st[t].r) return st[t].l;
int ls = t << 1 , rs = ls + 1;
if (st[ls].num >= st[rs].num) return findmx(ls);
return findmx(rs);
}
// 线段树部分 结束
int main()
{
// 读入
int n , q;
scanf("%d%d" , &n , &q);
// 离线存储
for (int i = 1; i <= q; i++){
int opid , id , k , x;
scanf("%d" , &opid);
if (opid == 1){
scanf("%d%d%d" , &id , &k , &x);
op[id].push_back({i , opid , k , x});
}else if (opid == 2){
scanf("%d%d" , &id , &k);
op[id].push_back({i , opid , k , -1});
}else {
scanf("%d" , &id);
op[id].push_back({i , opid , -1 , -1});
}
}
// 按每个队列分别处理
for (int i = 0 ; i < n ; i++){
cnt = 0;
for (auto &x : op[i]){
if (x.op == 1) dist[++cnt] = x.x;
}
// 离散化
sort(dist + 1 , dist + cnt + 1);
cnt = unique(dist + 1 , dist + cnt + 1) - dist - 1;
for (auto & x : op[i])
x.x = lower_bound(dist + 1 , dist + cnt + 1 , x.x) - dist;
// 1.因为要增加 和 删除 -> 存队列的顺序结构 -> 所以使用<栈>
stack<pair<int,int>> s;
// 2.因为要求众数 -> 存队列的值域结构 -> 所以使用<值域线段树>
// 需要支持:1.求最小众数 2.添加/删除
build(1 , cnt , 1);
for (auto & x : op[i]){
// cout << "现在处理第" << i << "个队列,操作为" << x.op << " " << x.k << " " << x.x << endl;
if (x.op == 1){
add(1 , x.x , x.k);
s.push({x.x , x.k});
}else if (x.op == 2){
int rest = x.k;
while (s.size() && rest > 0){
if (s.top().second <= rest){
add(1 , s.top().first , -s.top().second);
rest -= s.top().second;
s.pop();
}else {
add(1 , s.top().first , -rest);
s.top().second -= rest;
rest = 0;
}
}
}else {
if (st[1].num == 0) ans[x.id] = -1;
else {
ans[x.id] = dist[findmx(1)];
}
}
}
build(1 , cnt , 1);
}
for (int i = 1 ; i <= q ; i++){
if (!ans[i]) continue;
printf("%d\n" , ans[i]);
}
return 0;
}