数论分块 学习笔记
数论分块概述
数论分块可以快速计算一些含有除法向下取整的和式(即形如
它主要利用了富比尼定理(Fubini's theorem
NOTE
富比尼定理
又称「算两次
富比尼定理的积分形式:只要二重积分
积分号也是特殊的求和号,因此在一般求和中,富比尼定理往往呈现为更换计数顺序,即交换两个求和号。
组合数学中的富比尼定理表现为,用两种不同的方法计算同一个量,从而建立相等关系。
引理 1
证:
证毕
引理 2
证:
对于
对于
综上,得证
数论分块结论
对于常数
成立且满足
证明
令
实现过程
数论分块的过程大概如下:考虑和式
那么由于我们可以知道
利用上述结论,我们先求出
伪代码如下:
最终得到的
向上取整的数论分块
向上取整与前文所述的向下取整十分类似,但略有区别:
对于常数
成立且满足
证明
WARNING
当
N 维数论分块
求含有
一般我们用的较多的是二维形式,此时可将代码中 r = n / (n / i)
替换成 r = min(n / (n / i), m / (m / i))
。
应用例 1:Chain Reaction
题意
有一排
思路
太、太、太、太聪明了
! ! !
对于某个
而如果有
复杂度
AC 代码
#include <algorithm>
#include <cmath>
#include <ctime>
#include <iostream>
#include <map>
#include <queue>
#include <random>
#include <set>
#include <string>
#include <vector>
using namespace std;
mt19937_64 rnd(time(0));
typedef long long ll;
typedef uint64_t ull;
const ll INF = 1e18, Mod = 1e9 + 7;
ll n, m;
ll h[100010];
ll diff[100010];
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for (ll i = 1; i <= n; i++)
cin >> h[i], m = max(m, h[i]);
// cout<<m<<endl;
for (ll i = 1; i <= n; i++) {
if (h[i] > h[i - 1]) {
ll l = 1, r = 0;
while (l <= h[i]) {
if (l == h[i]) {
diff[l] += 1;
break;
}
r = min(m, (h[i] - 1) / ((h[i] - 1) / l));
// cout<<l<<' '<<r<<endl;
diff[l] += (h[i] + l - 1) / l;
diff[r + 1] -= (h[i] + l - 1) / l;
l = r + 1;
}
l = 1, r = 0;
// cout<<l<<' '<<r<<endl;
while (l <= h[i - 1]) {
if (l == h[i - 1]) {
diff[l] -= 1;
break;
}
r = min(m, (h[i - 1] - 1) / ((h[i - 1] - 1) / l));
// cout<<l<<' '<<r<<endl;
diff[l] -= (h[i - 1] + l - 1) / l;
diff[r + 1] += (h[i - 1] + l - 1) / l;
l = r + 1;
}
}
}
ll ans = 0;
for (ll i = 1; i <= m; i++) {
ans += diff[i];
cout << ans << ' ';
}
cout << endl;
return 0;
}
应用例 2:Monster
思路
枚举 加 k 攻击力 的模块的个数 然后用数论分块来在区间内跳跃遍历答案。
AC 代码
#include <algorithm>
#include <cmath>
#include <ctime>
#include <iostream>
#include <map>
#include <queue>
#include <random>
#include <set>
#include <string>
#include <vector>
#define OOO cout << ">>>>"; // 调试标识
using namespace std;
mt19937_64 rnd(time(0));
typedef long long ll;
typedef uint64_t ull;
const ll INF = 1e18, Mod = 1e9 + 7;
ll tt;
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> tt;
while (tt--) {
ll x, y, z, k, ans = INF;
cin >> x >> y >> z >> k;
for (ll r = 0;; r++) {
ll h = z - r * (r + 1) * k / 2, dmg = r * k,
cost = r * (k * x + y);
if (h <= 0) {
ans = min(ans, cost);
break;
} else
for (ll l = max(dmg, 1ll); l <= min(dmg + k, h);) {
ans = min(ans, cost + (l - dmg) * x +
(ll)ceil(1.0 * h / l) * y);
l = l ^ h ? ((h - 1) / ((h - 1) / l) + 1) : (h + 1);
}
}
cout << ans << endl;
}
cout.flush();
return 0;
}
其中保留了部分调试输出,可以使用它们来查看具体的处理过程,以深化对算法的理解。
数论分块扩展
以计算含有
的所有值,以及对每一种值求出哪些
- 因为
是单调不增的,所以对于所有 ,使得 的 必然是一段区间。 - 对于任意正整数
,我们对 与 的 分别分析,可以发现 ,取 得到 的一个上界为 。
这些结论与数论分块所需的引理相似,因此猜测可以写为数论分块形式。
结论是:使得式子
成立的最大的
证明
令
同理
又由
所以
进而
故原问题可以写为数论分块形式,代码与数论分块形式并无二异。
两个更加通用的结论
- 对于某个不超过
的正整数 ,使式子 成立的最大的 为 。 - 集合
的大小不超过 。
习题
CQOI2007 余数求和(需要一点转化和特判)
UVa11526 H(n)(几乎可以当做模板题)
POI2007 ZAP-Queries(数论分块一般配合 莫比乌斯反演 用以进一步降低复杂度;本题需要用到
这一条莫反结论)