【数论分块】数论分块算法模板及真题

1.数论分块的含义

数论分块算法,就是枚举出使得取整函数发生变化的地方。

例如,对表达式 ⌊ n i ⌋ \lfloor \frac{n}{i} \rfloor ⌊in⌋使用数论分块算法,就可以在 O ( n ) O(\sqrt n) O(n )的时间复杂度下枚举所有满足 ⌊ n i − 1 ⌋ + 1 = ⌊ n i ⌋ \lfloor \frac{n}{i-1}\rfloor+1 = \lfloor \frac{n}{i} \rfloor ⌊i−1n⌋+1=⌊in⌋的 i 。

2.算法模板

cpp 复制代码
long long r;
for(int l = 1; l <= n; l = r + 1)
{
		r = k/(k/l);
		/*
		your code
		*/
}

变量 l 就是取整函数发生变化的位置,即满足 ⌊ n l − 1 ⌋ + 1 = ⌊ n l ⌋ \lfloor \frac{n}{l-1}\rfloor+1 = \lfloor \frac{n}{l} \rfloor ⌊l−1n⌋+1=⌊ln⌋(l = 1除外)的位置,变量 r 就是满足 ⌊ n x ⌋ = ⌊ n l ⌋ \lfloor \frac{n}{x}\rfloor = \lfloor \frac{n}{l} \rfloor ⌊xn⌋=⌊ln⌋的所有x 中最大的一个。

直观上,该过程时间复杂度小于 O ( n ) O(n) O(n),因为每次往后跳的长度大于1,

该算法的实际复杂度为 O ( n ) O(\sqrt n) O(n )。

正确性证明和时间复杂度证明,详见此处。写题不需要证明。

3.算法的优化点

将所有 ⌊ n i ⌋ \lfloor \frac{n}{i} \rfloor ⌊in⌋结果一样的 i 一并取出,使得时间复杂度降为 O ( n ) O(\sqrt n) O(n )。

涉及取整的地方均可能用到此算法,包括但不限于,整除(练习题1 )、最大公因数(练习题3 )、最小公倍数(练习题2 )、取模(本文例题)。

4.例题

P2261 [CQOI2007] 余数求和

题目描述

给出正整数 n n n 和 k k k,请计算

G ( n , k ) = ∑ i = 1 n k   m o d   i G(n, k) = \sum_{i = 1}^n k \bmod i G(n,k)=i=1∑nkmodi

其中 k   m o d   i k\bmod i kmodi 表示 k k k 除以 i i i 的余数。

输入格式

输入只有一行两个整数,分别表示 n n n 和 k k k。

输出格式

输出一行一个整数表示答案。

输入输出样例 #1

输入 #1

复制代码
10 5

输出 #1

复制代码
29

说明/提示

样例 1 解释

G ( 10 , 5 ) = 0 + 1 + 2 + 1 + 0 + 5 + 5 + 5 + 5 + 5 = 29 G(10, 5)=0+1+2+1+0+5+5+5+5+5=29 G(10,5)=0+1+2+1+0+5+5+5+5+5=29。

数据规模与约定
  • 对于 30 % 30\% 30% 的数据,保证 n , k ≤ 1 0 3 n , k \leq 10^3 n,k≤103。
  • 对于 60 % 60\% 60% 的数据,保证 n , k ≤ 1 0 6 n, k \leq 10^6 n,k≤106。
  • 对于 100 % 100\% 100% 的数据,保证 1 ≤ n , k ≤ 1 0 9 1 \leq n, k \leq 10^9 1≤n,k≤109。

解题思路

  • a % b = a − ⌊ a b ⌋ ∗ b a \% b =a- \left \lfloor \frac{a}{b} \right \rfloor *b a%b=a−⌊ba⌋∗b
  • 求和时,前半部分和后半部分,分开处理。
  • 数列 a i = i ∗ ⌊ x i ⌋ a_i =i * \left \lfloor \frac{x}{i} \right \rfloor ai=i∗⌊ix⌋,在 ⌊ x i ⌋ \left \lfloor \frac{x}{i} \right \rfloor ⌊ix⌋为不变值的时候,是等差数列。

AC代码

cpp 复制代码
#include<bits/stdc++.h>
#define int long long

using namespace std;
typedef pair<int,int> PII;
const int mod = 998244353;
void solve()
{
   int n,k;
   cin >> n >> k;
   long long ans = 0;
   ans += n*k;
   int r;
   for(int i = 1; i <= n; i = r + 1)
   {
        if(k/i == 0) break;
        r = min(k/(k/i),n);
        ans -= (r-i+1)*(i+r)/2 *(k/i);
   }
   cout << ans << '\n';
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int t = 1;
    //cin >> t;
    while(t --)
    {
        solve();
    }
}

练习题

相关推荐
qianpeng8975 小时前
水声匹配场定位原理及实验
算法
董董灿是个攻城狮17 小时前
AI视觉连载8:传统 CV 之边缘检测
算法
AI软著研究员1 天前
程序员必看:软著不是“面子工程”,是代码的“法律保险”
算法
FunnySaltyFish1 天前
什么?Compose 把 GapBuffer 换成了 LinkBuffer?
算法·kotlin·android jetpack
颜酱1 天前
理解二叉树最近公共祖先(LCA):从基础到变种解析
javascript·后端·算法
地平线开发者2 天前
SparseDrive 模型导出与性能优化实战
算法·自动驾驶
董董灿是个攻城狮2 天前
大模型连载2:初步认识 tokenizer 的过程
算法
地平线开发者2 天前
地平线 VP 接口工程实践(一):hbVPRoiResize 接口功能、使用约束与典型问题总结
算法·自动驾驶
罗西的思考2 天前
AI Agent框架探秘:拆解 OpenHands(10)--- Runtime
人工智能·算法·机器学习
HXhlx2 天前
CART决策树基本原理
算法·机器学习