小H学物理
小H学物理
题目描述
小 H 今天在学校里学习了物理,物理老师给了小 H 一把能量枪,这把枪能发射波长为 $\lambda$ 的能量波,小 H 在一个长度为 $L$ 的设备上进行发射,一束能量值为 $e$ 能量波在 $x$ 位置发射,在 $x$ 处释放一次能量,该处能量值增加 $e$,能量波衰减能量绝对值减少一。当能量不为 $0$ 时继续向右传递 $\lambda$ 距离进行一次能量释放衰减过程,直到能量衰减为 $0$。
物理老师对小 H 发出了两种任务,任务一即发射能量波;任务二计算出设备上给定区间 $[l,r]$ 内的能量总和,请你帮他完成。
输入描述:
第一行输入两个整数 $K,L,\lambda$,分别表示任务总数和设备总长度以及波长。
接下来 $K$ 行,每行首先输入一个 $op$ 表示任务类型,
$op=0$,继续输入 $x_i, e_i$,表示能量波发射位置,能量波能量;
$op=1$,继续输入 $l_i, r_i$,表示每次查询的区间。
$1 \leq K \leq 10^5$
$1 \leq L \leq 10^5$
$1 \leq \lambda \leq 10$
$1 \leq x,l,r \leq L$
$1 \leq |e| \leq L$
输出描述:
对于每次查询,请输出对应区间内的能量总和。
示例1
输入
5 10 3
0 1 4
0 5 2
1 3 6
0 6 -2
1 1 9
输出
5
9
说明
第一次操作后设备上的能量为:$[4,0,0,3,0,0,2,0,0,1]$
第二次操作后设备上的能量为:$[4,0,0,3,2,0,2,1,0,1]$
第三次操作后设备上的能量为:$[4,0,0,3,2,−2,2,1,−1,1]$
解题思路
注意到每次修改操作只会对满足 $i \equiv x \pmod{\lambda}$ 的下标 $i$ 进行,且 $\lambda$ 是一个固定的很小的数。为此容易想到根据 $(i-1) \bmod \lambda$ 的结果把所有下标分成 $\lambda$ 个组。
对于修改操作,首先我们只会考虑第 $k = (x-1) \bmod \lambda$ 组内的下标。其中组内操作的区间左端点是第 $l = \left\lfloor\frac{x-1}{\lambda}\right\rfloor +1$ 个元素,右端点是第 $r = \min\left\{ l + |e| - 1, \left\lfloor\frac{n-1-k}{\lambda}\right\rfloor + 1 \right\}$(设第 $k$ 组内的元素个数为 $c$,有 $(c-1) \cdot \lambda + k + 1 \leq n \Rightarrow c = \left\lfloor\frac{n-1-k}{\lambda}\right\rfloor + 1$)。记 $p_i$ 是组内第 $i$ 个元素,因此修改操作就是给 $a_{p_l}, a_{p_{l+1}}, \ldots, a_{p_r}$ 加上一个首相为 $e$,公差为 $d = -\mathrm{sign}(e)$ 的等差数列,即 $a_{p_i} \gets a_{p_i} + e + (i - l) \cdot d \; (l \leq i \leq r)$。
对于查询操作,只需依次遍历 $\lambda$ 个组,分别求出每个组内满足 $p_i \in [l,r]$ 的元素(原数组下标)对应的 $a_{p_i}$ 的和即可。其中第 $k$ 组的求和区间 $[L,R]$ 要满足,$(L - 1) \cdot \lambda + k + 1 \geq l \Rightarrow L \geq \left\lceil \frac{l-1-k}{\lambda} \right\rceil + 1$,取 $L = \left\lceil \frac{l-1-k}{\lambda} \right\rceil + 1$。$(R - 1) \cdot \lambda + k + 1 \leq r \Rightarrow R \leq \left\lfloor \frac{r-1-k}{\lambda} \right\rfloor + 1$,取 $R = \left\lfloor \frac{r-1-k}{\lambda} \right\rfloor + 1$。另外只有 $L \leq R$ 才合法。
所以现在的问题是如何实现区间加等差数列,查询区间和。一般来说可以用线段树来实现,但从这题题解学到了树状数组的写法,因此记录一下推导过程。
记原数组为 $a$,$a$ 的差分数组为 $b$($b_i = a_i - a_{i-1}$),$b$ 的差分数组为 $c$($c_i = b_i - b_{i-1}$)。每次在 $a$ 的区间 $[l,r]$ 加上首项为 $k$ 公差为 $d$ 的等差数列,在对应的差分数组 $b$ 上等价于 $\displaylines{\begin{cases} b_l \gets b_l + k \\ b_i \gets b_i + d \; (l + 1 \leq i \leq r) \\ b_{r+1} \gets b_{r+1} - (k+(r-l) \cdot d) \end{cases}}$,在对应的差分数组 $c$ 上等价于 $\displaylines{\begin{cases} c_l \gets c_l + k \\ c_{l+1} \gets c_{l+1} - k + d \\ c_{r+1} \gets c_{r+1} - d - (k+(r-l) \cdot d) \\ c_{r+2} \gets c_{r+2} + k+(r-l) \cdot d \end{cases}}$。这样我们就可以把区间加操作转换成单点修改了。
对 $a$ 求前缀 $[1,r]$ 的和 $f(r)$ 等价于
\begin{align*}
f(r) = &\sum\limits_{i=1}^{r}{a_i} \\
= &\sum\limits_{i=1}^{r}{\sum\limits_{j=1}^{i}{b_j}} \\
= &\sum\limits_{i=1}^{r}{\sum\limits_{j=1}^{i}{\sum\limits_{k=1}^{j}{c_k}}} \\
= &\sum\limits_{i=1}^{r}{\sum\limits_{j=1}^{i}{c_1 + c_2 + \cdots + c_k + \cdots + c_j}} \\
= &\sum\limits_{i=1}^{r}{ic_1 + (i-1)c_2 + \cdots + (i-j+1)c_j + \cdots + c_i} \\
= & \frac{r(1+r)}{2}c_1 + \frac{(r-1)(1+r-1)}{2}c_2 + \cdots + \frac{(r-j+1)(1+r-j+1)}{2}c_j + \frac{(r-r+1)(1+r-r+1)}{2}c_r \\
\end{align*}
其中 $\frac{(r-j+1)(1+r-j+1)}{2}c_j = \frac{(r-j+1)(1+r-j+1)}{2}c_j = \frac{1}{2}\left( (r+1)(r+2)c_j - (2r+3)jc_j + c_j^2 \right)$,因此
\begin{align*}
f(r)=&\frac{r(1+r)}{2}c_1 + \frac{(r-1)(1+r-1)}{2}c_2 + \cdots + \frac{(r-j+1)(1+r-j+1)}{2}c_j + \frac{(r-r+1)(1+r-r+1)}{2}c_r \\
=&\frac{1}{2} \left( (r+1)(r+2)\sum\limits_{i=1}^{r}{c_i} -(2r+3)\sum\limits_{i=1}^{r}{ic_i} + \sum\limits_{i=1}^{r}{i^2c_i} \right)
\end{align*}
所以只需开三个树状数组分别维护 $c_i$,$ic_i$ 和 $i^2c_i$ 的前缀和即可。因此 $a$ 在区间 $[l,r]$ 的和就是 $f(r) - f(l-1)$。
在本题中需要对 $\lambda$ 个组分别开三个树状数组来维护。
AC 代码如下,时间复杂度为 $O\left(q\log{\frac{n}{\lambda}}\right)$:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5, M = 15;
LL tr[3][M][N];
int lowbit(int x) {
return x & -x;
}
void add(LL *tr, int x, LL c) {
for (int i = x; i < N; i += lowbit(i)) {
tr[i] += c;
}
}
LL query(LL *tr, int x) {
LL ret = 0;
for (int i = x; i; i -= lowbit(i)) {
ret += tr[i];
}
return ret;
}
LL pw(int x, int k) {
LL ret = 1;
while (k--) {
ret *= x;
}
return ret;
}
LL query(int k, int r) {
return (r + 1ll) * (r + 2) * query(tr[0][k], r) - (2 * r + 3) * query(tr[1][k], r) + query(tr[2][k], r) >> 1;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, k;
cin >> k >> n >> m;
while (k--) {
int op, x, y;
cin >> op >> x >> y;
if (!op) {
int k = (x - 1) % m, l = (x - 1) / m + 1, r = min((n - 1 - k) / m + 1, l + abs(y) - 1), d = y > 0 ? -1 : 1;
for (int i = 0; i < 3; i++) {
add(tr[i][k], l, pw(l, i) * y);
add(tr[i][k], l + 1, pw(l + 1, i) * (d - y));
add(tr[i][k], r + 1, pw(r + 1, i) * -(y + (r - l + 1) * d));
add(tr[i][k], r + 2, pw(r + 2, i) * (y + (r - l) * d));
}
}
else {
LL ret = 0;
for (int i = 0; i < m; i++) {
int l = (x - 1 - i + m - 1) / m + 1, r = (y - 1 - i) / m + 1;
if (l <= r) ret += query(i, r) - query(i, l - 1);
}
cout << ret << '\n';
}
}
return 0;
}
参考资料
牛客练习赛131题解:https://ac.nowcoder.com/discuss/1431386
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/18527481