CF1172F Nauuo and Bug
只想到一种离线后需要支持区间加,大于 \(p\) 的值减 \(p\),单点查操作的做法,但是并不会。
考虑到一个区间减 \(p\) 的次数是有限的,即 \(cnt \in [0,len]\)。如果我们能够对每个 \(cnt\) 求出传入哪个区间的数能够恰好减 \(cnt\) 个 \(p\) 的话,就可以快速求出 \(x\) 出进去后传出的是什么了。
考虑用线段树维护。设 \(c_x\) 表示经过当前节点的区间要想减 \(x\) 次 \(p\) 的最小的传入值,由此可以推出减 \(x\) 次对应的值的区间(\(c_x...c_{x+1}-1\))。
考虑如何合并两个节点。我们有一个 \(O(len_l \times len_r)\) 的做法:(是取最小值)
(需要同时符合大于等于 \(c_x\) 和大于等于 \(c_y+xp-sum_l\) 的条件)
显然这个是可以优化的。假设我们外层循环 \(x\),内层循环 \(y\)。发现 \(y\) 大到某种程度的时候,左区间的减 \(x\) 次的范围中最大的那个数都不能让右区间的减的次数达到 \(y\),那么当 \(y\) 更大的时候显然也是不可行的(不合法,可忽略),于是可以直接退出。
还可以发现,当 \(x\) 增加一, \(y\) 减少一的时候一定不会更优。考虑这种情况发生后,\(c_x\) 增大至少 \(p\),\(c_y+xp-sum_l\) 减少至少 \(0\)。如果原来 \(c_x\) 较大,那么现在显然不会更优。如果原来右边较大,那么原来肯定是 \(c_x ... c_{x+1}-1\) 中有一个数传进去后落到了 \(c_y\)(否则不合法可忽略,之前就不会到那里),如果现在 \(c_x\) 较大,不优(同上);如果现在 \(c_y+xp-sum_l\) 较大,那么一定是 \(c_x...c_{x+1}-1\) 中有一个数传进去后落到了 \(c_y\),而现在 \(x\) 增大了一,\(y\) 减小了一,显然是不可能的。
于是可以用指针扫描 \(O(len)\) 合并。
关键代码:
void build(int L, int R, int &cur) {
cur = ++ttot; sm[cur] = sum[R] - sum[L - 1];
if (L == R) {
vec[cur].push_back(-inf);
vec[cur].push_back(p - sm[cur]);
vec[cur].push_back(inf);
return ;
}
int mid = (L + R) >> 1; build(L, mid, ls[cur]); build(mid + 1, R, rs[cur]);
for (int i = 0; i <= R - L + 2; ++i) vec[cur].push_back(inf);
vec[cur][0] = -inf;
int ptr = 0, Ls = ls[cur], Rs = rs[cur];
for (int i = 0; i <= mid - L + 1; ++i) {
while (1) {
ll tmp = vec[Ls][i + 1] - 1 + sm[Ls] - 1ll * i * p;
if (tmp < vec[Rs][ptr]) {
if (ptr) --ptr; break;
}
MIN(vec[cur][i + ptr], max(vec[Ls][i], vec[Rs][ptr] + 1ll * i * p - sm[Ls]));
if (ptr + 1 > R - mid) break;
++ptr;
}
}
}
ll query(int L, int R, int l, int r, ll x, int cur) {
if (l <= L && R <= r) {
int cnt = upper_bound(vec[cur].begin(), vec[cur].end(), x) - vec[cur].begin() - 1;
return x - 1ll * cnt * p + sm[cur];
}
int mid = (L + R) >> 1;
if (l <= mid && r > mid) {
x = query(L, mid, l, r, x, ls[cur]);
return query(mid + 1, R, l, r, x, rs[cur]);
}
if (l <= mid) return query(L, mid, l, r, x, ls[cur]);
return query(mid + 1, R, l ,r, x, rs[cur]);
}