小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

posted @ 2024-11-05 11:20  onlyblues  阅读(18)  评论(0编辑  收藏  举报
Web Analytics