F - Clearance
F - Clearance
Problem Statement
AtCoder Inc.'s online shop currently handles $N$ products, and the stock of product $i$ is $A_i$ units remaining.
Process the following $Q$ orders in order. The $i$-th order is as follows:
- Buy $k_i$ units each of products $l_i,l_i+1,\dots,r_i$. For products with fewer than $k_i$ units, buy all available units. Report the total number of products bought in this order.
Note that for $i<Q$, the stock of products bought in the $i$-th order is reduced before proceeding to the $(i+1)$-th order.
Constraints
- All input values are integers.
- $1 \le N \le 3 \times 10^5$
- $1 \le A_i \le 10^{15}$
- $1 \le Q \le 3 \times 10^5$
- $1 \le l_i \le r_i \le N$
- $1 \le k_i \le 10^9$
Input
The input is given from Standard Input in the following format:
$N$
$A_1$ $A_2$ $\dots$ $A_N$
$Q$
$l_1$ $r_1$ $k_1$
$l_2$ $r_2$ $k_2$
$\vdots$
$l_Q$ $r_Q$ $k_Q$
Output
Output $Q$ lines.
The $i$-th line should contain the total number of products bought in the $i$-th order.
Sample Input 1
6
2 6 4 5 7 5
5
1 6 1
3 5 4
4 4 1
2 5 1
1 6 100
Sample Output 1
6
11
0
2
10
This input contains $5$ orders.
- Initially, the stocks of the products are (from product $1$ onward) $2,6,4,5,7,5$ units.
- The first order is $l_1 = 1, r_1 = 6, k_1 = 1$.
- In this order, $1,1,1,1,1,1$ units of the products are bought, for a total of $6$ units.
- After this, the stocks of the products become $1,5,3,4,6,4$ units.
- The second order is $l_2 = 3, r_2 = 5, k_2 = 4$.
- In this order, $0,0,3,4,4,0$ units of the products are bought, for a total of $11$ units.
- After this, the stocks of the products become $1,5,0,0,2,4$ units.
- The third order is $l_3 = 4, r_3 = 4, k_3 = 1$.
- In this order, $0,0,0,0,0,0$ units of the products are bought, for a total of $0$ units.
- After this, the stocks of the products become $1,5,0,0,2,4$ units.
- The fourth order is $l_4 = 2, r_4 = 5, k_4 = 1$.
- In this order, $0,1,0,0,1,0$ units of the products are bought, for a total of $2$ units.
- After this, the stocks of the products become $1,4,0,0,1,4$ units.
- The fifth order is $l_5 = 1, r_5 = 6, k_5 = 100$.
- In this order, $1,4,0,0,1,4$ units of the products are bought, for a total of $10$ units.
- After this, the stocks of the products become $0,0,0,0,0,0$ units.
解题思路
退坑半个多月了,因为有其他的事情要忙,加上 8 月底到 9 月中基本打一场掉一场,被搞到心态彻底炸了。不知道为什么感觉今年水平下滑很严重,而且现在基本都不考算法,大部分都是智力题,更加玩不动了。昨天复健打了把 abc,结果连之前稍微擅长的数据结构也不会了。然后今早 lc 又继续掉分,现在玩算竞已经完全没有正反馈了,唉。可能单纯因为沉默成本的原因我现在还在坚持打吧,反正是真想彻底弃坑了。
由于涉及到区间的查询和更新,因此容易想到用线段树来维护。问题的难点在于,当某个产品的数量不足 $k$ 时,如何处理区间内的产品。我们可以将区间 $[l,r]$ 内的产品根据剩余数量 $a_i$ 分成以下三类:数量至少为 $k$ 的产品($a_i \geq k$);数量不足 $k$ 但不为空的产品($0 < a_i < k$);空的产品($a_i = 0$)。对于第一类产品,每个产品的贡献为 $k$,更新时将其数量减少 $k$。对于第二类产品,每个产品的贡献为其现有数量 $a_i$ ,更新时将其数量置为 $0$。对于第三类产品,贡献为 $0$,更新时无需操作。
对于查询答案,我们可以换另外一种思路来计算。我们只考虑不为空的产品,假设这些产品每个都贡献 $k$ 个,那么总贡献为不为空的产品数乘以 $k$。另外还要考虑那些数量不足 $k$ 的产品,需要从总贡献中减去 $k-a_i$,相当于加上 $a_i-k$。而 $a_i-k$ 很容易获取,只需在线段树中对区间 $[l,r]$ 减去 $k$,如果某个产品的数量不足 $k$,那么其结果就为 $a_i-k$(不考虑区间中空的产品)。此时我们可以对整棵线段树进行多次二分依次找出小于 $0$ 的下标,从而找到数量不足 $k$ 但不为空的产品,并获取对应的 $a_i-k$。注意,在所有的操作中最多会二分线段树 $n$ 次,因为最多有 $n$ 个产品会变成空。
那么区间中原本为空的产品怎么处理呢?我们可以再开一个线段树(或树状数组)来标记它们的状态(设为 $1$ 即可),并在每次查询时更新。具体来说,在每次操作中,我们会标记变为空的产品,同时将这些产品的数量置为正无穷,以防止对区间减去 $k$ 后得到负数。这样就可以通过区间查询获取区间内为空的产品数量(从而反推不为空的产品数量),并且在二分线段树时,只到找当前操作变成空的产品。
AC 代码如下,时间复杂度为 $O(n+q\log{n})$:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 3e5 + 5;
const LL INF = 0x3f3f3f3f3f3f3f3f;
int n, m;
LL a[N];
struct Node {
int l, r;
LL mn, add;
}tr1[N * 4];
LL tr2[N];
void build(int u, int l, int r) {
tr1[u] = {l, r};
if (l == r) {
tr1[u].mn = a[l];
}
else {
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
tr1[u].mn = min(tr1[u << 1].mn, tr1[u << 1 | 1].mn);
}
}
void upd(int u, LL c) {
tr1[u].mn += c;
tr1[u].add += c;
}
void pushdown(int u) {
if (!tr1[u].add) return;
upd(u << 1, tr1[u].add);
upd(u << 1 | 1, tr1[u].add);
tr1[u].add = 0;
}
void modify(int u, int l, int r, LL c) {
if (tr1[u].l >= l && tr1[u].r <= r) {
upd(u, c);
}
else {
pushdown(u);
int mid = tr1[u].l + tr1[u].r >> 1;
if (l <= mid) modify(u << 1, l, r, c);
if (r >= mid + 1) modify(u << 1 | 1, l, r, c);
tr1[u].mn = min(tr1[u << 1].mn, tr1[u << 1 | 1].mn);
}
}
LL query(int u, int l, int r) {
if (tr1[u].l >= l && tr1[u].r <= r) return tr1[u].mn;
pushdown(u);
int mid = tr1[u].l + tr1[u].r >> 1;
if (r <= mid) return query(u << 1, l, r);
if (l >= mid + 1) return query(u << 1 | 1, l, r);
return min(query(u << 1, l, r), query(u << 1 | 1, l, r));
}
int query1(int u) {
if (tr1[u].l == tr1[u].r) return tr1[u].l;
pushdown(u);
if (tr1[u << 1].mn < 0) return query1(u << 1);
return query1(u << 1 | 1);
}
int lowbit(int x) {
return x & -x;
}
void add(int x) {
for (int i = x; i <= n; i += lowbit(i)) {
tr2[i]++;
}
}
int query2(int x) {
int ret = 0;
for (int i = x; i; i -= lowbit(i)) {
ret += tr2[i];
}
return ret;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
build(1, 1, n);
cin >> m;
while (m--) {
int l, r, c;
cin >> l >> r >> c;
LL ret = (r - l + 1ll - query2(r) + query2(l - 1)) * c;
modify(1, l, r, -c);
while (tr1[1].mn < 0) {
int x = query1(1);
ret += query(1, x, x);
modify(1, x, x, INF);
add(x);
}
cout << ret << '\n';
}
return 0;
}
参考资料
Editorial - AtCoder Beginner Contest 426:https://atcoder.jp/contests/abc426/editorial/14148
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/19127166