牛客寒假训练营第二场D-整除分块
牛客寒假训练营第二场D题
Description:
整除分块,又称数论分块。是数论算法中的重要技巧,你可以在各种需要枚举因子的连续求和类问题中见到它的身影。如杜教筛,莫比乌斯反演化简后的整除分段求和等。balabala
...(复制过来乱码,遂放弃),题目链接:https://ac.nowcoder.com/acm/contest/9982/D
题意就是\(S = \{\lfloor \dfrac{N}{i} \rfloor\}\),其中\(i\in[1, N]\),输入T,N,x。T表示测试的个数,求每个测试\(\lfloor \dfrac{N}{x} \rfloor\)在去重集合S中降序排序第几大。
示例输入
2 // 测试的个数
25 9
1000000000 1000000000
示例输出
8
63244
说明
当n=25时候,\(S=\{25,12,8,6,5,4,3,2,1\}\) \(\lfloor \dfrac{25}{9} \rfloor=2\),2在S集合中是降序排列的第8个,所以输出8
Solution:
1、首先了解一下整除分块的性质,对于一个正整数N
1、\(\lfloor \dfrac{N}{i} \rfloor\)最多有\(2\sqrt{N}\)种
证明:
- 当\(i \le \sqrt{N}\), 一共有\(\sqrt{N}\)种
- 当\(i > \sqrt{N}\) , \(\dfrac{N}{i} <= \sqrt{N}\), 最多有\(\sqrt{N}\)种;因为有可能对于所有的\(i > \sqrt{n}\),\(\dfrac{N}{i} < \sqrt{N}\),那么此时只有\(\sqrt{N} - 1\)种
根据这个题目别人的代码我可以知道:当\(\lfloor \sqrt{N} \rfloor = \lfloor \dfrac{N}{\sqrt{N}}\rfloor\)时,有\(2\sqrt{N} - 1\)种(不知道如何证明暂时)
2、\(b = \lfloor \dfrac{N}{a} \rfloor\) 时, \(a = \lfloor \dfrac{N}{b} \rfloor\)
这个类似于实数除法中除数和商的互逆性质:$b = \dfrac{N}{a} $ 时, \(a = \dfrac{N}{b}\)
不知如何证明
3、对于\(\lfloor \dfrac{N}{j} \rfloor\) 与\(\lfloor \dfrac{N}{i} \rfloor\)相等时,j最大的取值是\(\lfloor \dfrac{N}{\lfloor \dfrac{N}{i} \rfloor}\rfloor\)
也就是说\(\forall j \in [i, \lfloor \dfrac{N}{\lfloor \dfrac{N}{i} \rfloor}\rfloor]\), \(\lfloor \dfrac{N}{j} \rfloor = \lfloor \dfrac{N}{i} \rfloor\)
证明看这个哥哥的:https://www.cnblogs.com/0xfffe/p/9648943.html
2、本题解法
#include<bits/stdc++.h>
using namespace std;
int T, N, x;
int main()
{
scanf("%d", &T);
while(T--){
scanf("%d %d", &N, &x);
int sq = sqrt(N);
int tmp = N/x;
int tot;
// 求出S中集合元素个数(即有多少种数字)
if(sq==N/sq) tot = 2*sq-1;
else tot = 2*sq;
// 如果 x >= sqrt(n)
if(x>=sq) printf("%d\n",tot-tmp+1);
// else
else printf("%d\n",x); // N/x < sqrt(n)
}
return 0;
}
这里主要用到了性质1和2,
比如对于N=25,\(S=\{25,12,8,6,5,4,3,2,1\}\)
- x = 2时,25/2 = 12,ans = 2;也就是说ans = x,这是因为x 和 anx/x 在S中是关于\(\sqrt{N}\)对称的
- x = 10时,25/10 = 2, ans = |S| - 2+1 = 8; 即ans = |S| - N/x + 1
对于另一个类似的题目,主要用到性质3
求\(\sum_{i=1}^{N} \lfloor \dfrac{N}{i} \rfloor\)
// 求和模板
#define long long ll
void summ(ll N)
{
ll ans;
for(ll l=1, r; l<=n; l=r+1){
r = n/(n/i); // 区间右端点
ans += (r-l+1)*(n/i); // [l,r]区间内部的都是n/i
}
return ans;
}
参考
https://www.limstash.com/articles/201901/1206