【题解】洛谷 P10083 [GDKOI2024 提高组] 不休陀螺 [思维 + 树状数组 + st 表]
P10083 [GDKOI2024 提高组] 不休陀螺 - 洛谷 (luogu.com.cn)
首先考虑什么样的 区间是 “无限区间”:
(1)总收益不能是负收益。
(2)打完所有负收益的牌后,保证能打非负收益牌中花费最大的那一张。
(3)每一张负收益牌都能在打完其他负收益牌后打。
转化为公式:
(1)
(2)
(3)
化简:
我们发现 (2) 和 (3) 是比较特殊的。
(以下文段请区分递增与严格递增,递减与严格递减)
假设固定左端点 ,右端点
不断往右移动,如果当前
不满足 2 和 3 的性质,
由于 是递减的,
和
是递增的。
所以之后再右边的 就更不可能是答案,即每个
最右边的
是可以线性增加确定的。
确定了 后,把
间以
为左端点,负收益的右区间点减掉,就是当前
的答案。
假设 ,
对应的最右区间是
,那么
对应的
肯定大于等于
。
因为随着 的增长,
是递增的,约束条件更宽松。
之前满足条件的右区间点肯定还满足条件,所以 只增不减。
这很像双指针,我们考虑枚举 ,
继承上一个
。
边增加、边看看是否满足 (2) 和 (3),不满足立刻退出。
可以先定义一个数组 边移动指针边计算
。
和
可以用 st 表,
由于 号前面的都是
,所以只开一个 st 表,根据
的大小关系放进去
或者
。
判断负收益区间点可以用树状数组,每移动 一下就把当前
放进去,
退出循环时减掉树状数组上比 小的点,就是答案。
代码:
#include<bits/stdc++.h>
using namespace std;typedef long long LL;
const int N = 1e6 + 50;int n; LL E;
LL a[N], b[N];
LL c[N], sum_c[N], st[N][30];
LL tr_d[N];
LL cc[N];
map<LL, int> mp;void add(int x, LL d) {if (x == 0) {return ;}for (; x <= n; x += x & -x) {tr_d[x] += d;}
}LL get_sum(int x) {LL res = 0;for (; x >= 1; x -= x & -x) {res += tr_d[x];}return res;
}LL get_mx(int l, int r) {int lg = log2(r - l + 1);return max(st[l][lg], st[r - (1 << lg) + 1][lg]);
}void init_st_tree() {for (int i = 1; i <= n; i ++) {st[i][0] = (a[i] <= b[i]) ? a[i]: b[i];}for (int j = 1; j <= log2(n); j ++) {for (int i = 1; i + (1 << (j - 1)) <= n; i ++) {st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);}}memset(tr_d, 0, sizeof(tr_d));for (int i = 0; i <= n; i ++) {cc[i] = sum_c[i];}sort (cc, cc + n + 1);int nn = unique(cc, cc + n + 1) - cc - 1;for (int i = 0; i <= nn; i ++) { // 离散化,方便树状数组 mp[cc[i]] = i + 1; // + 1 防止下标为 0}
}int main () {freopen("top.in", "r", stdin);freopen("top.out", "w", stdout);ios::sync_with_stdio(false);cin.tie(0);cin >> n >> E;for (int i = 1; i <= n; i ++) {cin >> a[i];}for (int i = 1; i <= n; i ++) {cin >> b[i];}sum_c[0] = 0;for (int i = 1; i <= n; i ++) {c[i] = b[i] - a[i];sum_c[i] = sum_c[i - 1] + c[i];}init_st_tree();int r = 1;LL ans = 0;LL sum = 0; // 负收益总和 for (int l = 1; l <= n; l ++) {while (r <= n) {LL t = get_mx(l, r);if (E + sum + min(0ll, c[r]) >= t) {if (c[r] < 0) {sum += c[r];}add(mp[sum_c[r]], 1);r ++;}else {break;}}int xx = mp[sum_c[l - 1]] - 1; // 小于 sum_c[l - 1] 的(负收益段)LL tt = get_sum(xx);ans += (r - l) - tt; // 最右区间应该是 r - 1 if (r == l) {r = l + 1; // 预备下一个 }else {if (c[l] < 0) {sum -= c[l];}add(mp[sum_c[l]], -1);}}cout << ans << "\n";return 0;
}
