题解:P13514 [KOI 2025 #1] 干草堆
我的博客
“被卡常了”看着三个 TLE 的我这样想着,无意掠过题解,看到了同样的 FHQ,得知“可过”这一消息后便又开始努力的卡起常数。
通过后仔细看着那篇题解,才发现人家是 \(O(q\log n)\),我是 \(O(q\log^2n)\)……
正文
发现其实对于每一次询问可以二分干草堆的数量,然后问题就转变成了查询某一区间内前 \(k\) 大数的和是否大于 \(P_i\),这个可以用平衡树维护,但是删除和插入的次数是 \(O(nq)\) 的,显然无法接受,注意到我们可以离线,将问题按照 \(X_i\) 从小到大排序,这样就变成了可爱的只加不删,复杂度就有保证了。
关于具体实现,我写的是 FHQ,插入时的分裂写权值分裂,这样子维护的就是一个从大到小的序列了,查询时用排名分裂,就可以得到序列的前 \(k\) 个数字的和了。
复杂度是 \(O(q\log^2n)\),但常数有点大,不要全都用 long long 就好了。
代码
// code by 樓影沫瞬_Hz17
#include <bits/stdc++.h>
#define en_ putchar('\n')
#define e_ putchar(' ')
using lint = long long;
using namespace std;
template<class T> inline T in() {
T n = 0; char p = getchar();
while (p < '-') p = getchar();
bool f = p == '-' ? p = getchar() : 0;
do n = n * 10 + (p ^ 48), p = getchar();
while (isdigit(p));
return f ? -n : n;
}
template<class T> inline T in(T &a) { return a = in<T>(); }
template<class T> inline void out(T n) {
if(n < 0) putchar('-'), n = -n;
if(n > 9) out(n / 10);
putchar(n % 10 + '0');
}
template<class T1, class T2> T1 max(T1 a, T2 b) { return a > b ? a : a = b;}
template<class T1, class T2> T1 min(T1 a, T2 b) { return a < b ? a : a = b;}
constexpr int N = 3e5 + 10, B = 230;
using pii = pair<int, int>;
mt19937 rd(time(0));
int n, m;
struct query {
int x, p, id;
} q[N];
int d[N], ans[N];
// set<int> def
struct FHQ {
struct {
int l, r;
lint sum;
int sz, pri, val;
} t[N * 2];
int cnt, rt;
#define nnode(x) (t[++ cnt] = {0, 0, x, 1, (int)rd(), x}, cnt)
#define ls(u) t[u].l
#define rs(u) t[u].r
inline void up(int u) {
t[u].sz = t[ls(u)].sz + t[rs(u)].sz + 1;
t[u].sum = t[ls(u)].sum + t[rs(u)].sum + t[u].val;
}
inline void split(int u, int &l, int &r, int x) { // 权值分裂
if(!u) return void(l = r = 0);
if(t[u].val > x) // 注意大的在左子树
l = u, split(rs(u), rs(u), r, x);
if(t[u].val <= x)
r = u, split(ls(u), l, ls(u), x);
up(u);
}
inline void Split(int u, int &l, int &r, int k) { // 排名分裂
if(!u) return void(l = r = 0);
if(t[ls(u)].sz + 1 <= k)
l = u, Split(rs(u), rs(u), r, k - t[ls(u)].sz - 1);
else
r = u, Split(ls(u), l, ls(u), k);
up(u);
}
inline int merge(int l, int r) { // 合并
if(!l || !r) return l | r;
if(t[l].pri <= t[r].pri) {
rs(l) = merge(rs(l), r);
return up(l), l;
}
if(t[l].pri > t[r].pri) {
ls(r) = merge(l, ls(r));
return up(r), r;
}
}
inline void insert(int x) { // 插入
int l, r;
split(rt, l, r, x);
rt = merge(merge(l, nnode(x)), r);
}
inline lint get(int p) { // 求前 p 大的值
int l, r; lint tmp;
Split(rt, l, r, p);
tmp = t[l].sum;
rt = merge(l, r);
return tmp;
}
inline void debug(int u) { // 调试用,输出平衡树维护的序列
if(ls(u)) debug(ls(u));
out(t[u].val), e_;
if(rs(u)) debug(rs(u));
}
// 总和
#define all def.t[def.rt].sum
} def;
signed main() {
#ifndef ONLINE_JUDGE
freopen("i", "r", stdin);
freopen("o", "w", stdout);
#endif
in(n), in(m);
for(int i = 1; i <= n; i ++) in(d[i]);
for(int i = 1; i <= m; i ++) in(q[i].x), in(q[i].p), q[i].id = i;
sort(q + 1, q + 1 + m, [](query a, query b) { return a.x < b.x;}); // 问题离线并排序
int now = 1, l, r, mid;
for(int i = 1; i <= m; i ++) {
for( ; now <= q[i].x; now ++) def.insert(d[now]);
if(all < q[i].p) {
ans[q[i].id] = -1;
continue;
}
l = 0, r = q[i].x;
while(l <= r) { // 二分答案
mid = (l + r) >> 1;
if(def.get(mid) >= q[i].p) r = mid - 1;
else l = mid + 1;
}
ans[q[i].id] = l;
}
for(int i = 1; i <= m; i ++)
out(ans[i]), en_;
}
// 星間~ 干渉~ 融解~ 輪迴~ 邂逅~ 再生~ ララバイ~