数论分块大致用于处理形如求Σ(1,n) (k div i) 的问题
打表易得,(k div i)的值是线性的,因为向下取整,所以会出现值成段的现象,这样我们原先暴力的O(n)的算法可以得到优化
首先我们要知道一个定理
对于(k div i)而言最多有2√k个取值
证明:对于 i (1 <= i <= n, 且 i 是整数)而言,i 可以分成两种情况
- i <= √k , i 最多有 √k 个取值
- i >= √k , 那么 (k div i)<= √k,最多有√k个取值
所以我们可以拥有一个O(√k)的复杂度
对于怎么确定一个块值得左右边界,这里直接给出结论
对于一个块而言,假设左边界为LT,右边界为RT。那么有等式 RT = K / (K / LT)
所以我们就可以形如下式进行操作
for(ll l = 1, r = 0 ; l <= n ; l = r + 1) { if(k / l) r = min(n, k/(k/l)) ; else r = n ; do something }
为了显得这篇文章不那么划水,所以还是要带一题例题
BZOJ 1257
题意就是让我们求 Σ(1,n) (k mod i)
那么怎么转换到今天的知识呢?
我们都知道 k mod i = k - (k div i) * i
原式 = n* k - Σ(1,n) (k div i) * i
对于一个值块(l , r)而言 (k div i) 的值是确定的,我们可以把它看成常数,这里假设成T,那么对于值块(l , r) 后一块可转换成 (k div i) * Σ(l,r) i 等差数列求和
这题就搞定了
#include <bits/stdc++.h> using namespace std; #define ll long long ll n, k, ans = 0 ; int main(int argc, char const *argv[]) { scanf("%lld %lld",&n,&k) ; ans = k * n ; for(ll l = 1, r = 0 ; l <= n ; l = r + 1) { if(k / l) r = min(n, k/(k/l)) ; else r = n ; ans -= (k / l) * (r - l + 1) * (l + r) / 2ll ; } printf("%lld\n",ans) ; return 0; }
原文地址:https://www.cnblogs.com/wifePI/p/12337615.html
时间: 2024-11-09 02:32:35