小王的工作任务分配(hard ver.)
小王的工作任务分配(hard ver.)
题目描述
(本题 easy 和 hard 的区别在于,hard ver. 内存限制变小,easy ver. 没有 opt = 1)
每组数据集合涵盖一个长度为 $n$ 的项目难度数组 $a$(数组元素下标从 $1$ 起有序编排),以及 $m$ 条操作指令。操作指令具体分为以下两类:
调整指令($opt=1$):将数组 $a$ 中第 $x$ 个项目对应的难度层级数值更新为 $k$,即执行 $a[x]=k$ 操作;
统计指令($opt=2$):从下标为 $L$ 的项目起始,按照每隔 $y$ 个项目的规律选取项目,到项目 $R$ 结束(包括起始项目),统计这些项目的难度层级数值总和。
输入描述:
每个测试文件仅包含一组测试数据。
首行包含两个整数 $n$ 和 $m$($1 \leq n,m \leq 2 \times 10^5$),分别表示项目数量和操作指令数量;
第二行包含 $n$ 个整数,表示初始的项目难度数组 $a$($1 \leq a_i \leq 10^9$);
接下来 $m$ 行,每行首先有一个整数 $opt$($opt \in \{1,2\}$),代表一条操作指令。
对于 $opt=1$:输入 $x,k$($1 \leq x \leq n$,$1 \leq k \leq 10^9$)。
对于 $opt=2$:输入 $L,R,y$($1 \leq L \leq R \leq n$,$1 \leq x \leq n$)。
本题强制在线,所有输入的 $x,k,L,R,y$ 均需要异或 $lastans$,其定义为上一次操作 $2$ 的答案对 $2^{31}$ 取模的结果,若之前没有操作 $2$,则 $lastans=0$。请注意上面的数据范围均为解密后数值的数据范围,加密后的实际输入可能超过这个范围。
请注意此题的内存限制。
此题的数据量较大,注意常数优化。
输出描述:
对于每组数据中的每条统计指令($opt=2$),输出一行结果,为按照指令要求统计得到的项目难度层级数值总和。
示例1
输入
5 4
1 2 3 4 5
2 1 5 2
1 11 10
2 11 12 10
2 9 12 9
输出
9
8
11
解题思路
补一下上周日比赛的题,最近好多事要做都没多少时间做题了。出题人的题解完全看不懂,还是看别人的代码注释才弄懂的,难绷。
对于查询操作,假设查询的区间为 $[l,r]$,公差为 $d$,那么一次暴力查询的计算量为 $\left\lfloor\frac{r-l}{d}\right\rfloor + 1$,总的时间复杂度为 $O\left( \frac{qn}{d} \right)$。如果每次查询都取 $l=1$,$r=n$,$d=1$,显然会超时。这里有个常见的技巧是,如果时间复杂度是这种分式的类型,可以考虑一下有没有根号的做法。根号做法常见的有根号分治和分块。
先说一下小王的工作任务分配(easy ver.)的做法,我们只需关注查询操作即可。考虑是否存在根号分治的做法,意味着除了上述直接暴力枚举的做法,还需要另一种暴力方法。由于求的是某个区间内的满足公差的元素和,不难想到前缀和。因此对于每个不同的公差,我们都需要维护一个大小为 $n$ 的前缀和。具体地,定义 $s_{i,j}$ 表示公差为 $i$ 时元素 $a_{j \bmod i}, a_{(j \bmod i) + i}, a_{(j \bmod i) + 2i}, \ldots, a_{j}$ 的和($a$ 的下标从 $1$ 开始,额外定义 $a_0 = 0$),转移方程为 $s_{i,j} = s_{i, j - i} + a_j$。这种做法虽然可以实现 $O(1)$ 查询,但由于 $1 \leq d \leq n$,我们不可能维护 $n$ 个长度为 $n$ 的前缀和,因为时间与空间复杂度均为 $O\left(n^2\right)$。
此时我们久需要设定一个阈值 $M$,维护当 $d < M$ 时的前缀和,预处理的时间与空间复杂度为 $O(nM)$,查询的时间复杂度为 $O(1)$。而当 $d \geq M$ 则直接暴力枚举,一次查询的时间复杂度为 $O\left( \frac{n}{M} \right)$。取 $M = \sqrt{n}$,这样总的时间复杂度就是 $O(n \sqrt{n} + q \sqrt{n})$。
再把修改操作考虑进来,即使我们用树状数组去动态维护上述的前缀和,在时间也卡了过去,但由于空间限制到 32 MB,我们也开不了 $O(n \sqrt{n})$ 大小的数组。此时我们再考虑能否用分块的做法,与一般的分块做法一样,先定义块的大小 $M$,然后对于每个下标 $i \left(1 \leq i \leq n \right)$ 分配到编号为 $\left\lfloor\frac{i-1}{M}\right\rfloor$ 的块中,一共有 $\left\lceil\frac{n}{M}\right\rceil$ 个块。那每个块又需要维护什么信息呢?从查询的的角度考虑,由于我们需要求下标为 $l, l+d, l+2d, \ldots, l + \left\lfloor\frac{r-l}{d}\right\rfloor \cdot d$ 的元素和,其中 $l + \left\lfloor\frac{r-l}{d}\right\rfloor \cdot d$ 是不超过 $r$ 的且满足 $l + k \cdot d \left(k \in \mathbb{N}\right)$ 形式的最大值。容易知道这些下标都满足 $l + k \cdot d \equiv l \pmod{d}$,因此对于一个完整的块,我们需要快速地知道公差为 $d$ 时该块内满足 $i \equiv l \pmod{d}$ 的所有下标 $i$ 所对应元素的和。
这启发我们需要维护 $s_{i,j,k}$ 表示对于第 $i$ 个块,当公差为 $j$ 时所有满足模 $j$ 等于 $k$ 的下标所对应的元素的和,再设定一个阈值 $D$,维护 $ 1 \leq j < D$ 时每个块的信息。
所以对于查询操作,如果 $d < D$,我们至多枚举 $O\left(\frac{n}{M}\right)$ 个块和 $O\left(\frac{M}{D}\right)$ 个元素即可求出结果。否则 $d \geq D$,直接暴力枚举,时间复杂度为 $O\left(\frac{n}{D}\right)$。对于修改操作,除了令 $a_x \gets k$,还需要在 $x$ 所在的块,更新每个 $s_{\left\lfloor\frac{x-1}{M}\right\rfloor, d, x \bmod d} \gets s_{\left\lfloor\frac{x-1}{M}\right\rfloor, d, x \bmod d} - a_x + k \left(1 \leq d < D\right)$。
最后在下面的代码中取 $M = 450$,$D = 90$,是可以在时限内通过的。也可以尝试手动调参来找到最合适的参数,只能说内存只给 32 MB 还是有点太极端了。
AC 代码如下,时间复杂度为 $O\left( \sqrt{n} \cdot D + q \left( D + \frac{n}{D} + \sqrt{n} \right) \right)$:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 5, M = 450, K = 90;
int a[N], id[N];
LL s[M][K][K];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
id[i] = (i - 1) / M;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j < K; j++) {
s[id[i]][j][i % j] += a[i];
}
}
LL ret = 0;
while (m--) {
int op, l, r, d;
cin >> op >> l >> r;
l ^= ret & (1ll << 31) - 1, r ^= ret & (1ll << 31) - 1;
if (op == 1) {
for (int i = 1; i < K; i++) {
s[id[l]][i][l % i] += r - a[l];
}
a[l] = r;
}
else {
cin >> d;
d ^= ret & (1ll << 31) - 1;
ret = 0;
if (d >= K) {
while (l <= r) {
ret += a[l];
l += d;
}
}
else {
r = l + (r - l) / d * d;
int x = id[l], y = id[r];
while (l <= r && id[l] == x) {
ret += a[l];
l += d;
}
while (l <= r && id[r] == y) {
ret += a[r];
r -= d;
}
l %= d;
for (int i = x + 1; i < y; i++) {
ret += s[i][d][l];
}
}
cout << ret << '\n';
}
}
return 0;
}
参考资料
2025年第一届上海师范大学程序设计竞赛(题解部分):https://www.nowcoder.com/discuss/773973754765467648
圣诞不错 提交的代码:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=78165017
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/18993142

浙公网安备 33010602011771号