P5579 Siano

考点

  • 线段树
  • 二分

题解

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e5 + 50;
int n, m, a[maxn];

// mx:区间最大值
// s:区间和
// inc:区间固定的增值
// tag_add:增加标签
// tag_chop:切割标签
struct {
int l, r;
ll mx, s, inc;
ll tag_add, tag_chop;
#define l(x) tr[x].l
#define r(x) tr[x].r
#define mx(x) tr[x].mx
#define s(x) tr[x].s
#define inc(x) tr[x].inc
#define tag_add(x) tr[x].tag_add
#define tag_chop(x) tr[x].tag_chop
#define lc(x) x * 2
#define rc(x) x * 2 + 1
} tr[4 * maxn];

void up(int p) {
// 由于是有序,区间最大值肯定来源于右孩子
mx(p) = mx(rc(p));
s(p) = s(lc(p)) + s(rc(p));
}

void build(int p, int l, int r) {
l(p) = l, r(p) = r, tag_chop(p) = -1;
if (l == r) {
inc(p) = a[l];
return;
}
int mid = (l + r) / 2;
build(lc(p), l, mid), build(rc(p), mid + 1, r);
inc(p) = inc(lc(p)) + inc(rc(p));
}

// t:天数
void add(int p, ll t) {
s(p) += t * inc(p);
mx(p) += t * a[r(p)];
}

// v:阈值
void chop(int p, ll v) {
s(p) = (r(p) - l(p) + 1) * v;
mx(p) = v;
}

void down(int p) {
if (tag_chop(p) != -1) {
ll v = tag_chop(p);
chop(lc(p), v), chop(rc(p), v);
tag_chop(lc(p)) = tag_chop(rc(p)) = v;
tag_add(lc(p)) = tag_add(rc(p)) = 0;
tag_chop(p) = -1;
}
if (tag_add(p)) {
ll t = tag_add(p);
add(lc(p), t), add(rc(p), t);
tag_add(lc(p)) += t, tag_add(rc(p)) += t;
tag_add(p) = 0;
}
}

void update(int p, int l, int r, ll v) {
if (l <= l(p) && r(p) <= r) {
chop(p, v);
tag_add(p) = 0, tag_chop(p) = v;
return;
}
down(p);
int mid = (l(p) + r(p)) / 2;
if (l <= mid) update(lc(p), l, r, v);
if (r > mid) update(rc(p), l, r, v);
up(p);
}

// 大于v里选最小的,所以优先左孩子里选
int search(int p, ll v) {
if (l(p) == r(p)) return mx(p) > v ? l(p) : -1;
down(p);
int mid = (l(p) + r(p)) / 2, ans = -1;
// 左孩子最大值大于v,说明可能有解,向左走
if (mx(lc(p)) > v) ans = search(lc(p), v);
if (~ans) return ans;
return search(rc(p), v);
}

ll ask(int p, int l, int r) {
if (l <= l(p) && r(p) <= r) return s(p);
down(p);
int mid = (l(p) + r(p)) / 2;
ll ans = 0;
if (l <= mid) ans += ask(lc(p), l, r);
if (r > mid) ans += ask(rc(p), l, r);
return ans;
}

int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) scanf("%lld", &a[i]);
sort(a + 1, a + 1 + n);
build(1, 1, n);
ll pre = 0, t, d, v;
while (m--) {
scanf("%lld%lld", &d, &v);
t = d - pre;
pre = d;
add(1, t), tag_add(1) += t;
int idx = search(1, v);
if (idx == -1) {
printf("0\n");
continue;
}
ll ans = ask(1, idx, n) - (n - idx + 1) * v;
update(1, idx, n, v);
printf("%lld\n", ans);
}
return 0;
}

思路

一行草,按照a数组每天进行自增;

每次给你一个天数d和一个阈值v;要你在第d天时,删除大于v的部分,并输出删除部分的总和。

a数组对顺序不敏感,所以将其排序,变成一个升序数组,显然右半部分的草一定大于等于左半部分。

也就是说,割草这一操作范围就是idx ~ n,是一个类似后缀的范围。

idx就是大于v里下标最小的,由于数组已经有序,可以使用二分进行查找。

又因为是连续范围的修改,所以直接整合上线段树二分。


线段树的标签优先级需要注意,

同一区间,若先有tag_add再有tag_chop,那么前者会被后者覆盖掉,因为不管你加了多少,都得砍成tag_chop

若先有tag_chop再有tag_add,那么tag_add可以累加。

所以在下传标签时,应该先传tag_chop,再传tag_add