JOISC2024 Day1A Fish 另解
\(\text{By hhoppitree.}\)
\(\textbf{JOISC2024 Day1A. Fish}\)
题目大意
给定一个长度为 \(N\) 的序列 \(C\),\(Q\) 次询问区间 \([L_i,R_i]\),表示若初始有一个长度为 \(R_i-L_i+1\) 的全 \(0\) 数组 \(A\),每次操作可单点加 \(D\) 或者将一个后缀加上 \(1\),求最少单点加操作次数使得 \(A_i=C_{L+i-1}\),或输出无解。
数据范围:\(N,Q\le3\times10^5\),\(\texttt{2s/1GB}\)。
思路分析
考虑将操作倒序进行,那么相当于将每次询问的子段进行最少的单点减 \(D\) 操作使得其非严格不降,这可以从后到前贪心,得到了一个 \(\mathcal{O}(NQ)\) 的算法。
考虑使用线段树维护,那么我们可以先将拆分出的每个段内先进行调整后再合并调整,例如段 \([L,mid),[mid,R)\),可以先将 \([L,mid)\) 和 \([mid,R)\) 分别调整后对 \([L,mid)\) 的后缀进行调整。
调整可以被拆成若干个后缀减 \(D\),我们只需对于每一个线段树的区间 \([L,R]\) 快速计算要求 \(C_R\le x\) 时最少的操作次数以及此时 \(C_L\) 的值即可。
将 \(i,i+1\)(经过提前调整后必然有 \(C_i\le C_{i+1}\)) 之间视作有 \(\left\lfloor\dfrac{C_{i+1}-C_i}{D}\right\rfloor\) 个空隙,\(C_L\) 前有 \(\infty\) 个空隙,那么每次就是将 \([L,R]\) 的最靠后的一个空隙对应的后缀进行后缀减 \(D\)。
而我们要进行 \(\left\lceil\dfrac{x-C_R}{D}\right\rceil\) 次这样的操作,后缀和后二分计算出对应的值即可。
时间复杂度为 \(\mathcal{O}(N\log^2N)\),将询问离线后归并计算可以做到 \(\mathcal{O}(N\log N)\)(视 \(N,Q\) 同阶)。
代码呈现
实现了 \(\mathcal{O}(N\log^2N)\) 的算法。
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 5;
long long a[N], D;
vector<long long> arr[1 << 20];
long long opt[1 << 20], sc[1 << 20], pos[1 << 20];
vector< tuple<long long, long long, long long> > z[1 << 20];
void upd(int k) {
for (int i = 0; i + 1 < (int)arr[k].size(); ++i) {
z[k].push_back({sc[k], i + 1, pos[k]});
sc[k] += (arr[k][i] - arr[k][i + 1]) / D;
pos[k] += (arr[k][i] - arr[k][i + 1]) / D * (i + 1);
}
}
void build(int k, int l, int r) {
if (l == r) {
arr[k].push_back(a[l]);
upd(k);
return;
}
int mid = (l + r) >> 1;
build(k << 1, l, mid);
build(k << 1 | 1, mid + 1, r);
opt[k] = opt[k << 1] + opt[k << 1 | 1];
arr[k] = arr[k << 1 | 1];
for (auto x : arr[k << 1]) {
if (x > arr[k].back()) {
opt[k] += ((x - arr[k].back() + D - 1) / D);
x -= D * ((x - arr[k].back() + D - 1) / D);
}
arr[k].push_back(x);
}
upd(k);
}
pair<long long, long long> query(int k, int l, int r, int x, int y, long long w) {
if (l >= x && r <= y) {
if (w >= arr[k][0]) return {arr[k].back(), opt[k]};
long long o = (arr[k][0] - w + D - 1) / D;
if (sc[k] <= o) {
return {arr[k].back() - (o - sc[k]) * D, opt[k] + pos[k] + (o - sc[k]) * (r - l + 1)};
}
auto [a, b, c] = *--upper_bound(z[k].begin(), z[k].end(), tuple<long long, long long, long long>{o, 0, 0});
return {arr[k].back(), opt[k] + c + (o - a) * b};
}
int mid = (l + r) >> 1;
if (y <= mid) return query(k << 1, l, mid, x, y, w);
if (x > mid) return query(k << 1 | 1, mid + 1, r, x, y, w);
pair<long long, long long> rs = query(k << 1 | 1, mid + 1, r, x, y, w);
pair<long long, long long> ls = query(k << 1, l, mid, x, y, rs.first);
return {ls.first, ls.second + rs.second};
}
signed main() {
int n; scanf("%d%lld", &n, &D);
for (int i = 1; i <= n; ++i) scanf("%lld", &a[i]);
build(1, 1, n);
int q; scanf("%d", &q);
while (q--) {
int l, r; scanf("%d%d", &l, &r);
auto [x, y] = query(1, 1, n, l, r, 1e18);
if (x < 0) puts("-1");
else printf("%lld\n", y);
}
return 0;
}