CF1208E 一种单调栈做法
CC__DIAMOND · · 题解
本题的所有题解似乎都是单调队列和线段树,然而它其实也可以是一道思维量与细节都很高的单调栈好题。
根据题意,数组不能平移到方框之外,所以对于每个数组的每一个元素,它能够做出贡献的范围是一个区间。如果我们用
for (int x = 1; x <= len; ++x) {
r[x] = x + w - len;
while (top && v[s[top]] <= v[x]) {
r[s[top]] = min(r[s[top]], x - 1);
--top;
}
s[++top] = x;
}
top = 0;
for (int x = len; x; --x) {
l[x] = x;
while (top && v[s[top]] < v[x]) {
l[s[top]] = max(l[s[top]], x + w - len + 1);
--top;
}
s[++top] = x;
}
top = 0;
具体原理就是在单调栈中维护一个严格不递增的序列,在每个元素入栈时把它的左端点设为
// 万能头与IO略(本题输入很大,建议采用快读)
namespace Solution {
constexpr int max_w = 1e6 + 10;
int l[max_w], r[max_w], v[max_w]; // 每个元素的大小于区间
int s[max_w], top = 0; // 手写栈
ll d[max_w]; // 答案的差分数组
void solve() {
int n, w;
IO::io.read(n);
IO::io.read(w);
for (int i = 1; i <= n; ++i) {
int len;
IO::io.read(len);
for (int i = 1; i <= len; ++i) IO::io.read(v[i]);
for (int i = 1; i <= len; ++i) {
r[i] = i + w - len;
while (top && v[s[top]] <= v[i]) {
r[s[top]] = min(r[s[top]], i - 1); // 小于等于当前元素的元素贡献的区间最右到 i-1(这里的i其实是x但是懒得改了)
--top;
}
s[++top] = i;
}
top = 0;
for (int i = len; i; --i) {
l[i] = i;
while (top && v[s[top]] < v[i]) {
l[s[top]] = max(l[s[top]], i + w - len + 1);
--top;
}
s[++top] = i;
}
top = 0;
// 数组一定会覆盖的区域,超出的部分负数的贡献无效
int ml = w - len + 1, mr = len;
for (int i = 1; i <= len; ++i) {
if (v[i] < 0) l[i] = max(l[i], ml), r[i] = min(r[i], mr);
if (l[i] > r[i]) continue;
d[l[i]] += v[i];
d[r[i] + 1] -= v[i];
}
}
ll sum = 0;
for (int i = 1; i <= w; ++i) {
sum += d[i];
IO::io.write(sum);
IO::io.push(' ');
}
IO::io.push('\n');
}
}
int main() {
Solution::solve();
return 0;
}