P5609 [Ynoi2013] 对数据结构的爱 题解
我永远喜欢数据结构。
显然 $l,r$ 的答案 $\in\{\sum\limits_{i=l}^ra_i-zp|z\in\mathbf{N}\}$,考虑求出 $z$。
线段树。每个节点上维护 $k_i$ 表示 $z=i$ 时,进入该节点时的当前答案最小值,
显然,若进入该节点时当前答案为 $g$,则 $k_z\le g<k_{z+1}$,容易二分求出 $z$。
考虑怎么算 $k_i$。对在左孩子里减去 $x$ 个 $p$,在右孩子里减去 $y$ 个 $p$ 的情况,有
$$ \begin{cases} k_{x+y}\ge\text{lson}.k_x\\ k_{x+y}+\sum\limits_{i\in\text{lson}}a_i-xp\ge\text{rson}.k_y\\ \text{lson}.k_{x+1}-1+\sum\limits_{i\in\text{lson}}a_i-xp\ge\text{rson}.k_y \end{cases} $$
即,$k_{x+y}=\min\limits_{\text{lson}.k_{x+1}-1+\sum\limits_{i\in\text{lson}}a_i-xp\ge\text{rson}.k_y}\{\max\{\text{lson}.k_x,\text{rson}.k_y-\sum\limits_{i\in\text{lson}}a_i+xp\}\}$。
显然 $k$ 有单调性,双指针维护条件 $\text{lson}.k_{x+1}-1+\sum\limits_{i\in\text{lson}}a_i-xp\ge\text{rson}.k_y$ 即可。
#include <cstdio>
#include <algorithm>
using namespace std;
int n, m;
long long P;
struct T
{
T *l, *r;
int s, t;
long long v, *k;
T(int s, int t) : s(s), t(t), k(new long long[t - s + 3])
{
k[0] = -1e18;
for (int i = 1; i <= t - s + 2; ++i)
k[i] = 1e18;
}
void u()
{
v = l->v + r->v;
for (int x = 0, y = 0; x <= l->t - l->s + 1; ++x)
{
y -= y == r->t - r->s + 2;
for (; y <= r->t - r->s + 1; ++y)
{
if (l->k[x + 1] - 1 + l->v - x * P < r->k[y])
{
--y;
break;
}
k[x + y] = min(k[x + y], max(l->k[x], r->k[y] - l->v + x * P));
}
}
}
} * r;
void B(int s, int t, T *&p)
{
p = new T(s, t);
if (s == t)
return (void)(scanf("%lld", &p->v), p->k[1] = P - p->v);
int m = p->s + p->t >> 1;
B(s, m, p->l);
B(m + 1, t, p->r);
p->u();
}
long long Q(int l, int r, long long g, T *p)
{
if (l <= p->s && p->t <= r)
return g + p->v - P * (upper_bound(p->k, p->k + p->t - p->s + 3, g) - p->k - 1);
int m = p->s + p->t >> 1;
if (l <= m && r > m)
return Q(l, r, Q(l, r, g, p->l), p->r);
if (l <= m)
return Q(l, r, g, p->l);
if (r > m)
return Q(l, r, g, p->r);
}
int main()
{
scanf("%d%d%lld", &n, &m, &P);
B(1, n, r);
for (int i = 0, x, y; i < m; ++i)
scanf("%d%d", &x, &y), printf("%lld\n", Q(x, y, 0, r));
return 0;
}