Even(23Nowcoder6.J)(二分+可持久化线段树)
题意:
给定一个序列\(a\),定义一次操作选择序列中一个元素\(a[i]\), 使\(a_i = \lfloor \frac{a_i}{2} \rfloor\),其中\(a_i\)为当前序列中的最大偶数,若没有则是最大奇数。
有\(q\)组询问,每次给定\(k, l, r\)分别表示操作次数和操作区间,每次回答操作完成后区间中的\(Max\),询问间互相独立。
\(n\le10^4, a_i\le 10^9, 1\le l\le r \le n,k\le 10^9\)
Solution:
\(Hint1\) 不管奇数还是偶数,操作次数始终是\(\log{a_i}\)次,所以一共能操作\(n * \log{a_i}\)次
\(Hint2\) 考虑一次询问的\(k=10^9\),区间为\([1,n]\)次的情况,如果那么每次一操作的元素下标是一定的,令该序列为\(Operations\)
\(Hint3\) 考虑获取这个序列,优先队列模拟即可
\(Hint4\) 考虑任意选择区间,\(k=10^9\),容易知道此时的操作序列一定为\(Operations\)的一个子序列,令该序列为\(SubOperations\), 那随着\(k\)缩小,新的操作序列一定为\(SubOperations\)的一个前缀
\(Hint5\) 考虑以建立\(|Operations|\)棵线段树,每棵线段树内部维护信息为\(cnt和Max\),内部下标为\([1, n]\),那么第\(i\)棵线段树中\(cnt\)记录执行到第\(i\)次操作时对区间的操作次数,那么\(Max\)就是维护一个区间最大值,每次操作都是单点修改,只会改变一条链,故建立可持久化线段树。
\(Hint6\) 考虑解决询问,我们只要每次二分这\(|Operations|\)个版本,找到第一个区间内操作次数\(\ge k\)的版本即可,然后输出区间最大值即为答案
时间复杂度\(O(qlog^2+n\log^2)\),空间复杂度\(O(nlog^2)\)
比题解低能,但是思想简单,写法简单,但是没写出来🤡
Code:
int n, q;
int a[maxn];
struct node {
int x, id;
bool operator<(const node &t) const {
if (t.x % 2 != x % 2) return x % 2 > t.x % 2;
if (x != t.x) return x < t.x;
return id > t.id;
}
};
int idx;
int root[maxn << 5], ls[maxn << 9], rs[maxn << 9], cnt[maxn << 9], Max[maxn << 9];
void build(int &rt, int l, int r) {
rt = ++idx;
if (l == r) {
Max[rt] = a[l];
return;
}
int mid = l + r >> 1;
build(ls[rt], l, mid);
build(rs[rt], mid + 1, r);
Max[rt] = max(Max[ls[rt]], Max[rs[rt]]);
}
void update(int rtu, int &rtv, int l, int r, int pos, int val) {
rtv = ++idx;
ls[rtv] = ls[rtu], rs[rtv] = rs[rtu];
cnt[rtv] = cnt[rtu]; Max[rtv] = Max[rtu];
if (l == r) {
Max[rtv] = (a[l] >>= 1);
cnt[rtv] += 1;
return ;
}
int mid = l + r >> 1;
if (pos <= mid) update(ls[rtu], ls[rtv], l, mid, pos, val);
else update(rs[rtu], rs[rtv], mid + 1, r, pos, val);
cnt[rtv] = cnt[ls[rtv]] + cnt[rs[rtv]];
Max[rtv] = max(Max[ls[rtv]], Max[rs[rtv]]);
}
int query( int rt, int l, int r, int ql, int qr) {
if (ql <= l && r <= qr) return cnt[rt];
int mid = l + r >> 1, res = 0;
if (ql <= mid) res += query(ls[rt], l, mid, ql, qr);
if (mid < qr) res += query(rs[rt], mid + 1, r, ql, qr);
return res;
}
int query_Max(int rt, int l, int r, int ql, int qr) {
if (ql <= l && r <= qr) return Max[rt];
int mid = l + r >> 1, res = 0;
if (ql <= mid) res = max(query_Max(ls[rt], l, mid, ql, qr), res);
if (mid < qr) res = max(query_Max(rs[rt], mid + 1, r, ql, qr), res);
return res;
}
priority_queue<node> Q;
void solve(int cas) {
cin >> n >> q;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
if (a[i]) Q.push((node){a[i], i});
}
build(root[0], 1, n);
int cnt = 0;
while (!Q.empty()) {
node p = Q.top(); Q.pop();
auto [x, id] = p;
update(root[cnt], root[cnt + 1], 1, n, id, 1);
cnt++;
if (x / 2 > 0) Q.push({x >>= 1, id});
}
for (int i = 1; i <= q; ++i) {
int l, r, k; cin >> l >> r >> k;
int L = 1, R = cnt, t = 0;
while (L <= R) {
int mid = L + R >> 1;
if (query(root[mid], 1, n, l, r) <= k) {
t = mid;
L = mid + 1;
} else R = mid - 1;
}
cout << query_Max(root[t], 1, n, l, r) << '\n';
}
}