数论分块(整除分块) 每日一遍,算法再见!

数论分块(整除分块)

证明+推导

在这里插入图片描述
在这里插入图片描述

模板

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
	ll n;
	cin>>n;
	ll ans=0;
	/*初始化l为1,因为我们从1开始遍历*/
	for(ll l=1,r;l<=n;l=r+1)//下一个值的l就等于r+1,这个不难理解
	{
		r=n/(n/l);//我们找到最大的r,使得n/r==n/l
		ans+=(r-l+1)*(n/l);//接着我们加上求和的值
	}
	cout<<ans<<'\n';
	return 0;
}

例题分析

下面我们来一道稍微复杂一点的题目
C. Floor and Mod
题目大意:给你一个数给你x,y,让你找出有多少对(a,b)满足, ⌊ a b ⌋ = \left \lfloor \frac{a}{b}\right \rfloor= ba=a mod b,其中 1 < = a < = x , 1 < = b < = y 1<=a<=x,1<=b<=y 1<=a<=x,1<=b<=y.

等式右边,a mod b = a- ⌊ a b ⌋ ∗ b \left \lfloor \frac{a}{b}\right \rfloor*b bab
-> ⌊ a b ⌋ = a − ⌊ a b ⌋ ∗ b \left \lfloor \frac{a}{b}\right \rfloor=a-\left \lfloor \frac{a}{b}\right \rfloor*b ba=abab
-> ⌊ a b ⌋ ∗ ( b + 1 ) = a \left \lfloor \frac{a}{b}\right \rfloor*(b+1)=a ba(b+1)=a
-> ⌊ a b ⌋ = a b + 1 \left \lfloor \frac{a}{b}\right \rfloor=\frac{a}{b+1} ba=b+1a
我们令k= ⌊ a b ⌋ \left \lfloor \frac{a}{b}\right \rfloor ba,k = a b + 1 \frac{a}{b+1} b+1a
因为k = a mod b,所以 k ∈ [ 0 , b − 1 ] k\in[0,b-1] k[0,b1]
所以 a = k ∗ ( b + 1 ) , a ∈ [ 0 , b 2 − 1 ] a=k*(b+1),a\in[0,b^{2}-1] a=k(b+1),a[0,b21]
问题就变成了 ∑ b = 1 y m i n ( x , b 2 − 1 ) b + 1 \sum_{b=1}^{y}\frac{min(x,b^2-1)}{b+1} b=1yb+1min(x,b21),如果枚举b的话显然会超时
所以分块求和
第一部分:当 b 2 − 1 < = x , b < = x + 1 , 此 时 b 2 − 1 b + 1 = b − 1 b^2-1<=x,b<=\sqrt{x+1},此时\frac{b^2-1}{b+1}=b-1 b21<=x,b<=x+1 b+1b21=b1,此时 m i n ( x , b 2 − 1 ) min(x,b^2-1) min(x,b21)就是 b 2 − 1 b^2-1 b21,令 t = x + 1 t=\sqrt{x+1} t=x+1 我们对第一部分求和得到, ∑ b = 1 m i n ( y , t ) ( b − 1 ) \sum_{b=1}^{min(y,t)}(b-1) b=1min(y,t)(b1),这就是一个等差数列求和,时间复杂度 O 1 O1 O1

第二部分我们,我们在求和第二部分之前先要特判一下有没有第二部分,如果第一部分的t>=y,那么就没有第二部分了,如果t<y,那么我们继续求第二部分, ∑ b = t + 1 y m i n ( x , b 2 − 1 ) b + 1 \sum_{b=t+1}^{y}\frac{min(x,b^2-1)}{b+1} b=t+1yb+1min(x,b21),此时 m i n ( x , b 2 − 1 ) min(x,b^2-1) min(x,b21)就是x了, ∑ b = t + 1 y x b + 1 \sum_{b=t+1}^{y}\frac{x}{b+1} b=t+1yb+1x,这里的求和也不要暴力遍历b,直接用我们上面的数论整除分块求和模板即可复杂度是 O n O\sqrt{n} On

最后我们将两部分结果加起来就得到了答案

AC代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
	ll T;
	cin>>T;
	while(T--)
	{
		ll ans=0;
		ll x,y;
		cin>>x>>y;
		ll t = sqrt(x+1);//我们先找到第一部分和第二部分的分界点t 
		if(t>y)//我们这里判断有没有第二部分,如果只有第一部分就只求和第一部分,这里是没有第二部分的情况 
		{
			t=y;
			ans+=(t-1)*t/2;//这里是等差数列求和,套公式即可 
		}
		/*这里是有第二部分的情况*/ 
		else{
		ans+=(t-1)*t/2;//我们对第一部分求和
		/*这里我们就用数论的整除分块来求取第二部分*/ 
		for(ll l=t+1,r;l<=min(x,y);l=r+1) 
		{
			if (x / (l + 1) == 0||l>y) break;//如果出现除式结果为0,或者l的值超过了y就结束循环 
			r = min(y, min(x, x / (x / (l + 1)) - 1));//注意我们这里r不能超过x,y的最小值 
			ans+=(r-l+1)*(x/(l+1));
		}
	   }
		cout<<ans<<'\n';
	}
	return 0;
}

请关注我看更多数论分支知识体系讲解,也别忘点个赞额!

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值