专栏文章
报社天狗解题报告
P11772题解参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @miqa9tdt
- 此快照首次捕获于
- 2025/12/04 01:31 3 个月前
- 此快照最后确认于
- 2025/12/04 01:31 3 个月前
简要题意
给定数组 且初始全为 ,数组大小均为 。 组操作如下:
- 单点修改 或 ,其中
- 每次查询给定 ,查询该式:,其中 表示 的因子数
。
约定与性质
为了方便处理,我们将 数组向上平移 ,即求 。
有一个常数是 以内 最大为 。
算法一
考虑答案从 到 的变化量,处理出来。时间复杂度 。
期望得分: pts。
算法二
对于 subtest 2,转化题意为给定序列 ,二元组集合 ,其中 。单点修改,全局查询 。使用根号分治可以做到 。也可以使用询问分块,时间复杂度相同,存在在线方式。也可以转化题意成 ,然后使用 k-d tree 做,时间复杂度 。
该档期望得分: pts,结合 subtest 1 可获得 pts。
算法三
考虑分块打表后使用解法 1 中的递推求解。令块长为 ,则有递推部分复杂度为 。但由于打表的值会改变,考虑直接维护其值。变换式子形式为
。转化一下,变成
前半部分因为对于每一个表,它的数论分块是静态的,直接维护每一块查询的 的和的值;对于后半部分,我们考虑枚举 的值,用
vector 存储对应的 ,二分找到位置树状数组维护即可,时间复杂度 。平衡一下可以做到 。该档期望得分: pts(大概不卡常吧),结合以上可得 pts。
算法四
令 。
然后,我们变换一下式子形态
这时,对于满足 在 范围内的 ,我们将 放入 集合中。而 中只有少部分集合元素很多,其余的都很少。这里我们如果以元素数量是否大于 来断言少与多的话,则集合元素很多的集合只有 个,而集合元素很少的集合的元素总数量只有 。
我们令 。
我们定义 为集合元素很多的集合的集合, 为集合元素很少的集合的集合。
于是我们分为两部分讨论。对于集合元素很少的集合我们直接暴力枚举判断即可。而另外一部分,我们可以先数论分块,令目前的 的值为 ,即只需要查询区间中 的 的数量与 的和。查询需要做到 。
考虑维护上面我们需要查询的东西。尽管我们查询的是二维前缀和,但有一维很小(只有 ),我们可以在修改时对于每一行修改,这样就没有影响了。这样,只需要我们对于每一行维护一个 的分块即可。这样我们每次修改就是 了。当然,我们也需要一个 的分块维护 的前缀和。
总时间复杂度为 ,空间复杂度为 。
期望得分: pts。
Code
此代码未经卡常,不是最后一版。
CPP#include <bits/stdc++.h>
using namespace std;
using ci = const int;
using u32 = uint32_t;
using i64 = int64_t;
using u64 = uint64_t;
template <class T>
inline void Max(T &x, const T &y) noexcept {
if (x < y) x = y;
}
template <class T>
inline void Min(T &x, const T &y) noexcept {
if (y < x) x = y;
}
/**
* @brief Fast input & output
* @param read Input
*/
namespace IO {
template <class T>
void read(T &x) {
x = 0;
static char ch;
bool sm(0);
while (!isdigit(ch = getchar())) sm = ch == '-';
while (x = 10 * x + (ch ^ 48), isdigit(ch = getchar()));
if (sm) x = -x;
}
template <class T>
void write(T x) {
if (x < 0) putchar('-'), x = -x;
static char q[40];
char *qt(q);
do *qt++ = x % 10 ^ 48;
while (x /= 10);
do putchar(*--qt);
while (qt != q);
}
template <class T, class... Ts>
void read(T &x, Ts &...rest) {
read(x);
read(rest...);
}
} // namespace IO
using IO::read;
using IO::write;
const int N = 1e6 + 5;
const int MaxD = 240;
const int MasterD = 13;
const int CutNumber = 200;
vector<int> vd[MaxD + 5]; // sets with the same number of factors
vector<int> ld; // numbers with fewer factors
bool tong[N];
int pri[N], d[N];
int a[N], b[N];
// numbers with more factors
int md[MasterD], md_t, md_i[MaxD + 5];
/**
* @brief to speed up the change of add on array
* @param l the begin of the array
* @param r the end of the array
* @param v the value of the addition
*/
void AddArray(i64 *l, i64 *r, ci &v) noexcept {
#define did(i) (l[i] += v)
#define _(s) \
case s: \
did(s - 1);
while (l < r) {
switch (r - l) {
default:
did(7);
_(7) _(6) _(5) _(4) _(3) _(2) _(1)
}
l += 8;
}
#undef _
#undef did
}
/**
* @brief O(sqrt n) -> O(1)
* @details Only for array B
*/
struct Block2 {
i64 b1[1 << 10], b2[1 << 20];
void add(int x, int y) noexcept {
AddArray(b1 + (x >> 10), b1 + (1 << 10), y);
AddArray(b2 + x, b2 + (x >> 10 << 10) + (1 << 10), y);
}
i64 ask(int x) noexcept {
return ((x >> 10) ? b1[(x >> 10) - 1] : 0) + b2[x];
}
};
/**
* @brief O(sqrt[3] n) -> O(1)
* @details Only for array A
*/
struct Block3 {
static const int B = 127, BB = (1 << 14) - (1 << 7);
i64 b1[1 << 7], b2[1 << 14], b3[1 << 21];
void add(int x, int y) noexcept {
AddArray(b1 + (x >> 14), b1 + (1 << 7), y);
AddArray(b2 + (x >> 7), b2 + ((x >> 7) & BB) + (1 << 7), y);
AddArray(b3 + x, b3 + (x >> 7 << 7) + (1 << 7), y);
}
i64 ask(int x) noexcept {
return ((x >> 14) ? b1[(x >> 14) - 1] : 0) +
((x >> 7) & B ? b2[(x >> 7) - 1] : 0) + b3[x];
}
};
int mxd[N];
// to initialize something about the factors.
void init_d(ci n) {
memset(md_i, 0xff, sizeof md_i);
d[1] = 1;
int *pt = pri;
for (int i = 2; i <= n; ++i) {
if (!tong[i]) *pt++ = i, d[i] = 2;
for (int *j = pri; i * *j <= n; ++j) {
tong[i * *j] = 1;
d[i * *j] = d[i] << 1;
if (!(i % *j)) {
d[i * *j] -= d[i / *j];
break;
}
}
}
for (int i = 1; i <= n; ++i)
if (i * d[i] <= n) vd[d[i]].push_back(i);
for (int i = 1; i <= MaxD; ++i) {
if (vd[i].size() > CutNumber) {
md_i[i] = md_t, md[md_t++] = i;
} else {
for (ci &x : vd[i]) ld.push_back(x);
}
}
for (int i = 1; i <= MaxD; ++i)
if (!~md_i[i]) md_i[i] = md_i[i - 1];
for (int i = 1; i <= n; ++i) mxd[i] = max(mxd[i - 1], d[i]);
}
Block2 B;
Block3 A2, A[MasterD];
void changeA(int x, int y) noexcept {
if (x > N - 5) return;
// delta of a[x]
int dlt = y - a[x];
a[x] = y;
if (x * d[x] > N - 5) return;
A2.add(x, dlt);
if (md_i[d[x]] != md_i[d[x] - 1]) {
for (int i = md_i[d[x]]; i != md_t; ++i) A[i].add(x, dlt);
}
}
void changeB(int x, int y) noexcept {
if (++x > N - 5) return;
B.add(x, y - b[x]);
b[x] = y;
}
i64 query(int n) noexcept {
i64 ans(0);
static Block3 *_A;
int m = n / mxd[n];
for (int l = m + 1, r; l <= n; l = r + 1) {
r = n / (n / l);
int t = md_i[min(MaxD, n / l)];
if (!~t) continue;
_A = &A[t];
ans += (_A->ask(r) - _A->ask(l - 1)) * B.ask(n / l);
}
for (int i = 0; i != md_t; ++i)
ans -= (A[i].ask(n / md[i]) - (i ? A[i - 1].ask(n / md[i]) : 0)) *
B.ask(md[i]);
for (ci &i : ld)
if (i * d[i] <= n) {
if (i > m)
ans += a[i] * (B.ask(n / i) - B.ask(d[i]));
else
ans -= a[i] * B.ask(d[i]);
}
for (int l = 1, r; l <= m; l = r + 1) {
r = min(n / (n / l), m);
ans += B.ask(n / l) * (A2.ask(r) - A2.ask(l - 1));
}
return ans;
}
int main() {
init_d(N - 5);
for (int i = 1; i <= N - 5; ++i) changeA(i, 1), b[i] = 1;
for (int i = 0; i < (1 << 10); ++i) {
B.b1[i] = ((i + 1) << 10);
iota(B.b2 + (i << 10), B.b2 + (i << 10) + (1 << 10), 1);
}
int Q;
read(Q);
while (Q--) {
static int opt, n, x, y;
read(opt);
switch (opt) {
case 1:
read(n);
write(query(n));
putchar('\n');
break;
case 2:
read(x, y);
changeA(x, y);
break;
case 3:
read(x, y);
changeB(x, y);
break;
}
}
return 0;
}
常数优化
查询的前面很大一部分是一定满足条件的,而后半部分的询问数较少,分组计算即可。
bonus
该题的瓶颈在于 的维护,单次查询复杂度 ,可否优化?
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...