[这是题目链接]: https://codeforces.com/gym/105161/problem/E

题意

给定一个长度为n的整数序列 。定义 Reduce 操作:对序列中指定区间([l,r])(即(a_{l},\cdots,a_{r}) ),将该区间内索引最小的最大值除以2(向下取整) 。有q个查询,每个查询给出l、r、k三个整数,需计算在区间([l,r])上执行k次 Reduce 操作后的最大值,且每次查询相互独立,都基于初始给定序列进行计算。

思路

根据题意,每次操作都是在区间上找到最大值进行除$2$操作,进行$k$次,找出操作完后的最大值,我们可以将所有数$a_{i} $除以$2^n$($n=1,2,3…$),直到$a_{i}$变成$0$,将所有中间算出的数存起来,这样题目等价于求这些所有数的第$k+1$大的数,由于每次查询都是基于初始给定序列进行计算,我们可以使用主席树进行维护。

[主席树可以参考这个模板]: https://www.luogu.com.cn/problem/P3834

  • 注意这题空间要开大一点,因为存的数字不仅仅是原始数组中的数,还有除以$2^n$这些数

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include<bits/stdc++.h>
using namespace std;
//#define int long long
const int M =1e5+2;
int n,q,m,cnt;
int a[M];
int tree[M*600],root[M],le[M*600],ri[M*600];
int update(int i,int l,int r,int x)
{
int rt=++cnt;
le[rt]=le[i];
ri[rt]=ri[i];
tree[rt]=tree[i]+1;
if(l<r)
{
int mid=(l+r)>>1;
if(x<=mid) le[rt]=update(le[i],l,mid,x);
else ri[rt]=update(ri[i],mid+1,r,x);
}
return rt;
}
int query(int u,int v,int l,int r,int k)
{
if(l==r) return l;
int mid=(l+r)>>1;
int t=tree[ri[v]]-tree[ri[u]];
if(t>=k) return query(ri[u],ri[v],mid+1,r,k);
else return query(le[u],le[v],l,mid,k-t);
}
int build(int l,int r)
{
int rt=++cnt;
tree[rt]=0;
if(l<r)
{
int mid=(l+r)>>1;
le[rt]=build(l,mid);
ri[rt]=build(mid+1,r);
}
return rt;
}
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>q;
int t;
root[0]=build(0,1e5);
for(int i=1;i<=n;i++)
{
cin>>a[i];
t=a[i];
bool as=0;
while(t)
{
if(!as)
{
root[i]=update(root[i-1],0,1e5,t);
as=1;
}
else
{
root[i]=update(root[i],0,1e5,t);
}
t>>=1;
}
if(!as)
{
root[i]=update(root[i-1],0,1e5,t);
as=1;
}
else
{
root[i]=update(root[i],0,1e5,t);
}
}
while(q--)
{
int l,r,k;
cin>>l>>r>>k;
cout<<query(root[l-1],root[r],0,1e5,k+1)<<'\n';
}
return 0;
}