Description
给定序列 a=(a1,a2,⋯ ,an)a=(a_1,a_2,\cdots,a_n)a=(a1,a2,⋯,an),有 mmm 个操作分两种:
- subtract(l,r,x)\operatorname{subtract}(l,r,x)subtract(l,r,x):对每个 i∈[l,r]i\in[l,r]i∈[l,r] 执行 ai←ai−x×[ai>x]a_i\gets a_i-x\times [a_i>x]ai←ai−x×[ai>x].
- query(l,r)\operatorname{query}(l,r)query(l,r):求 ∑i=lrai,mini=lrai,maxi=lrai\sum\limits_{i=l}^r a_i,\min\limits_{i=l}^r a_i,\max\limits_{i=l}^r a_ii=l∑rai,i=lminrai,i=lmaxrai.
Limitations
1≤n,m≤5×1051\le n,m\le 5\times 10^51≤n,m≤5×105
1≤ai,x≤1091\le a_i,x\le 10^91≤ai,x≤109
1≤l≤r≤n1\le l\le r\le n1≤l≤r≤n
6s,64MB6\text{s},\textcolor{red}{64\text{MB}}6s,64MB
Solution
首先考虑一种势能线段树:
每个节点维护所管辖区间的最值,在执行 subtract\operatorname{subtract}subtract 时:
- 若当前 max<x\max< xmax<x,操作没有用,直接退出.
- 若当前 min>x\min> xmin>x,所有数都会被改,给当前节点打上一个减去 xxx 的标记.
但是这玩意的复杂度是错的,只要给一个 1,1091,10^91,109 交替的序列,每次 subtract(1,n,1)\operatorname{subtract}(1,n,1)subtract(1,n,1) 就被卡成 O(nm)O(nm)O(nm) 了.
序列分块似乎也做不了,只能对值域分块,由于 VVV 可达 10910^9109,所以要用倍增分块,将 [bi,bi+1)[b^i,b^{i+1})[bi,bi+1) 分为一块,总共有 ⌈logbV⌉\lceil \log_b V\rceil⌈logbV⌉ 块,其中 bbb 为底数.
对每个块维护一棵上面所述的线段树,容易发现每个数最多减 bbb 次就会掉出该块管辖范围,至于这些掉出的元素,因为一个数最多掉出 O(logbV)O(\log_b V)O(logbV) 次,可以直接暴力找块插入.
这样做的时间复杂度为 O(nblognlogbV)O(nb\log n\log_b V)O(nblognlogbV),空间复杂度为 O(nlogbV)O(n \log_b V)O(nlogbV).
然而由于常数比较大,bbb 取 222 是不够的,需要调大.
空间限制很紧,但是强制在线无法逐块处理,注意到线段树的底下几层很浪费空间,于是若节点的长度小于一个阈值 LLL 时,就直接暴力处理.
bbb 和 LLL 需要根据自己的代码情况调,笔者取的是 b=32,L=32b=32,L=32b=32,L=32.
还有其他细节需要注意:
- 线段树节点不要存 l,rl,rl,r,容易被卡.
- 底层暴力时处理插入时,不要给新插入的元素错误地打上标记.
- 区间的元素不再是 r−l+1r-l+1r−l+1 个,需要维护 cnt\textit{cnt}cnt 表示元素个数.
- 由于所有线段树底层共用一个 aaa,需要先判断是否在本块范围内再操作.
Code
6.83KB,4.37s,12.50MB (in total, C++20 with O2)6.83\text{KB},4.37\text{s},12.50\text{MB}\;\texttt{(in total, C++20 with O2)}6.83KB,4.37s,12.50MB(in total, C++20 with O2)
只给主要部分.
namespace block {
struct data {
i64 sum;
int max, min, cnt;
inline data() : sum(0), max(0), min(inf), cnt(0) {}
inline data(i64 _sum, int _max, int _min, int _cnt)
: sum(_sum), max(_max), min(_min), cnt(_cnt) {}
};
inline data operator+(const data& a, const data& b) {
return data(
a.sum + b.sum,
std::max(a.max, b.max),
std::min(a.min, b.min),
a.cnt + b.cnt
);
}
inline int ls(int u) { return 2 * u + 1; }
inline int rs(int u) { return 2 * u + 2; }
struct segment_tree {
int n, bl, br;
vector<data> info;
vector<int> tag;
vector<int>::iterator a;
vector<bool>::iterator fall;
inline segment_tree() {}
inline void init(int _n, int _bl, int _br) {
n = _n, bl = _bl, br = _br;
const int cap = std::max(1, n >> 3);
info.resize(cap), tag.resize(cap);
}
inline void leaf_remove(int u, int l, int r, int ql, int qr, int v, vector<int>& trash) {
data res;
for (int i = l; i <= r; i++)
if (bl <= a[i] && a[i] <= br) {
a[i] -= tag[u];
if (ql <= i && i <= qr && a[i] > v) {
a[i] -= v;
if (a[i] < bl) {
trash.push_back(i);
fall[i] = true;
continue;
}
}
res = res + data(a[i], a[i], a[i], 1);
}
info[u] = res;
tag[u] = 0;
}
inline void leaf_insert(int u, int l, int r) {
data res;
for (int i = l; i <= r; i++)
if (bl <= a[i] && a[i] <= br) {
if (!fall[i]) a[i] -= tag[u];
else fall[i] = false;
res = res + data(a[i], a[i], a[i], 1);
}
info[u] = res;
tag[u] = 0;
}
inline data leaf_query(int u, int l, int r, int ql, int qr) {
data res;
for (int i = l; i <= r; i++)
if (bl <= a[i] && a[i] <= br) {
a[i] -= tag[u];
if (ql <= i && i <= qr)
res = res + data(a[i], a[i], a[i], 1);
}
tag[u] = 0;
return res;
}
inline void pushup(int u) {
info[u] = info[ls(u)] + info[rs(u)];
}
inline void apply(int u, int k) {
tag[u] += k;
info[u].sum -= 1LL * info[u].cnt * k;
info[u].max -= k, info[u].min -= k;
}
inline void pushdown(int u) {
if (tag[u]) {
apply(ls(u), tag[u]);
apply(rs(u), tag[u]);
tag[u] = 0;
}
}
void build(int u, int l, int r) {
if (r - l < L) return void(info[u] = leaf_query(u, l, r, l, r));
const int mid = (l + r) >> 1;
build(ls(u), l, mid);
build(rs(u), mid + 1, r);
pushup(u);
}
void insert(int u, int l, int r, int i) {
if (r - l < L) return leaf_insert(u, l, r);
const int mid = (l + r) >> 1;
pushdown(u);
if (i <= mid) insert(ls(u), l, mid, i);
else insert(rs(u), mid + 1, r, i);
pushup(u);
}
void modify(int u, int l, int r, int ql, int qr, int v, vector<int>& trash) {
if (info[u].max <= v) return;
if (ql <= l && r <= qr && info[u].min - v >= bl) return apply(u, v);
if (r - l < L) return leaf_remove(u, l, r, ql, qr, v, trash);
const int mid = (l + r) >> 1;
pushdown(u);
if (ql <= mid) modify(ls(u), l, mid, ql, qr, v, trash);
if (qr > mid) modify(rs(u), mid + 1, r, ql, qr, v, trash);
pushup(u);
}
data query(int u, int l, int r, int ql, int qr) {
if (ql <= l && r <= qr) return info[u];
if (r - l < L) return leaf_query(u, l, r, ql, qr);
const int mid = (l + r) >> 1;
pushdown(u);
if (qr <= mid) return query(ls(u), l, mid, ql, qr);
else if (ql > mid) return query(rs(u), mid + 1, r, ql, qr);
else return query(ls(u), l, mid, ql, qr) + query(rs(u), mid + 1, r, ql, qr);
}
inline void insert(int i) {
insert(0, 0, n - 1, i);
}
inline void subtract(int l, int r, int v, vector<int>& trash) {
modify(0, 0, n - 1, l, r, v, trash);
}
inline data query(int l, int r) {
return query(0, 0, n - 1, l, r);
}
inline void build() {
build(0, 0, n - 1);
}
};
struct blocks {
int n;
vector<segment_tree> sgt;
vector<int> trash;
vector<bool> fall;
vector<int>::iterator a;
inline blocks() {}
inline blocks(int _n) : n(_n) {}
inline void init(vector<int>& a) {
sgt.resize(B + 1);
fall.resize(n);
for (int j = 0; j <= B; j++) {
const int bl = 1 << (j * B);
const int br = (1 << ((j + 1) * B)) - 1;
sgt[j].init(n, bl, br);
sgt[j].a = a.begin();
sgt[j].fall = fall.begin();
sgt[j].build();
}
this->a = a.begin();
}
inline void subtract(int l, int r, int x) {
for (int j = 0; j <= B; j++) {
sgt[j].subtract(l, r, x, trash);
if (j > 0) {
for (auto i : trash)
for (int k = 0; k < j; k++)
if (sgt[k].bl <= a[i] && a[i] <= sgt[k].br) {
sgt[k].insert(i);
break;
}
trash.clear();
}
}
}
inline data query(int l, int r) {
data res;
for (int j = 0; j <= B; j++) res = res + sgt[j].query(l, r);
return res;
}
};
}

被折叠的 条评论
为什么被折叠?



