题解Code+7 教科书般的亵渎
Posted tweetuzki
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了题解Code+7 教科书般的亵渎相关的知识,希望对你有一定的参考价值。
题意:
有一个集合 (S),初始为空。进行 (m) 次操作哟,分下列两种:
- 修改:向集合内添加一个 ([1, n]) 内的正整数 (h)。
- 定义 (f(d)) 为 ([1, d], [d + 1, 2d], [2d + 1, 3d], ldots) 这样取,第一次没有在集合内找到对应区间内元素的回数。询问:给出 (l, r),求 (sum limits_{d = l} ^ r f(d))。
(n le 10 ^ 5),(m le 10 ^ 6)。
比赛时只会这题,自闭。
答案是调和级数 (mathcal{O}(n log n)) 级别的,考虑对每个 (d) 都用个 ( exttt{std::set}) 维护它的所有还没有覆盖集合内元素的区间,那么这个 (d) 对应的答案就是这些区间中最小的那个的编号,(l, r) 对应的答案直接树状数组求个区间和。
修改时,只要保证枚举到的每个区间都是还没有被覆盖的,这样均摊下去复杂度就是对的。难点在如何维护这个东西。
首先,跨越 (h) 的没有被覆盖的区间只和 (h) 在 (S) 中的前驱和后缀有关。分别记为 (pre, nxt)。
那么我们要找到的区间,设起点为 (x),长度为 (l)((l mid x)),大概是这样的:(pre le x lt h le x + l lt nxt)(也可以写作 (pre le x - l lt h le x lt nxt))。
我们在 ([pre, h)) 和 ([h, nxt)) 较短一侧内枚举起点 (x),然后二分找到 (x) 最早跨到另外一个区间的因子,不断删除这样的区间直到跨到区间之外。就完成了只枚举到了所有还未被覆盖的跨越 (h) 的区间,维护直接 ( exttt{std::set}) 上删除,复杂度是 (mathcal{O}(n log^2 n))(也可以用可删堆维护得到常数小一些的 (log^2))。
但这里枚举起点和二分的复杂度又是怎么保证的呢?这其实就是一个把大区间 ([pre, nxt)) 分成 ([pre, h)) 和 ([h, nxt)) 的过程,即启发式分裂,是 (mathcal{O}(n log n)) 的,再加上一个二分就是 (mathcal{O}(n log^2 n))。
每次查询时只要到树状数组里查,复杂度 (mathcal{O}(m log n))。
故总时间复杂度 (mathcal{O}(n log^2 n + m log n))。
代码:
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <set>
#include <vector>
const int MaxN = 100000;
int N, M;
std::set<int> A;
std::set<int> S[MaxN + 5];
std::vector<int> Fac[MaxN + 5];
struct FenwickTree {
long long t[MaxN + 5];
inline int lowbit(int i) { return i & -i; }
inline void update(int x, long long v) { for (int i = x; i <= N; i += lowbit(i)) t[i] += v; }
inline long long query(int x) { long long res = 0; for (int i = x; i > 0; i -= lowbit(i)) res += t[i]; return res; }
inline long long qrange(int l, int r) { return query(r) - query(l - 1); }
};
FenwickTree T;
void solve() {
for (int i = 1; i <= N; ++i)
for (int j = 1; (j - 1) * i <= N; ++j)
S[i].insert(j);
for (int i = 1; i <= N; ++i) {
for (int j = 0; j * i <= N; ++j)
Fac[i * j].push_back(i);
S[i].insert(*(--S[i].end()) + 1);
}
for (int i = 1; i <= N; ++i)
T.update(i, *S[i].begin());
A.insert(0), A.insert(2 * N);
for (int q = 1; q <= M; ++q) {
int opt;
scanf("%d", &opt);
if (opt == 1) {
int x;
scanf("%d", &x);
if (A.count(x) != 0) continue;
std::set<int>::iterator iter_nxt = A.lower_bound(x), iter_pre = iter_nxt;
iter_pre--;
int pre = *iter_pre, nxt = *iter_nxt;
A.insert(x);
if (x - pre < nxt - x) {
for (int i = pre; i < x; ++i) {
std::vector<int>::iterator it = std::lower_bound(Fac[i].begin(), Fac[i].end(), x - i);
while (it != Fac[i].end()) {
int d = *it;
if (i + d >= nxt) break;
int blocknum = (x - 1) / d + 1;
T.update(d, -*S[d].begin());
S[d].erase(blocknum);
T.update(d, *S[d].begin());
it++;
}
}
} else {
for (int i = x; i < nxt; ++i) {
std::vector<int>::iterator it = std::upper_bound(Fac[i].begin(), Fac[i].end(), i - x);
while (it != Fac[i].end()) {
int d = *it;
if (i - d < pre) break;
int blocknum = (x - 1) / d + 1;
T.update(d, -*S[d].begin());
S[d].erase(blocknum);
T.update(d, *S[d].begin());
it++;
}
}
}
} else {
int l, r;
scanf("%d %d", &l, &r);
printf("%lld
", T.qrange(l, r));
}
}
}
int main() {
scanf("%d %d", &N, &M);
solve();
return 0;
}
以上是关于题解Code+7 教科书般的亵渎的主要内容,如果未能解决你的问题,请参考以下文章