【BZOJ 1257】数论分块

数论分块:在某个区间内 \left \lfloor\frac{n}{i}\right \rfloor 为一定值,将该值相同的区间分为一块。

对于含有 \left \lfloor\frac{n}{i}\right \rfloor 的求和式子,对于任意一个 i (i <= n),我们需要找到一个最大的 j (i <= j <= n)使得 \left \lfloor \frac{n}{j} \right \rfloor = \left \lfloor \frac{n}{i} \right \rfloor

j = \left \lfloor \frac{n}{\left \lfloor \frac{n}{i} \right \rfloor} \right \rfloor,这个很好得出,既然我们要对含有 \left \lfloor\frac{n}{i}\right \rfloor 的式子求和,那我们就要找到区间[i,j]的都含有 \left \lfloor\frac{n}{i}\right \rfloor。假设 i 是第一次出现满足 \left \lfloor\frac{n}{i}\right \rfloor 要求的下标,那么显然这段区间[i,j]内都是 \left \lfloor\frac{n}{i}\right \rfloor 的倍数,所以用 n 除以 \left \lfloor\frac{n}{i}\right \rfloor ,就得到了最大的那个下标。然后每次以[i,j]为一块,进行分块求和。

证明:

                                                                          \left \lfloor \frac{n}{i} \right \rfloor \leq \frac{n}{i}

                                                                    \Rightarrow      \left \lfloor \frac{n}{\left \lfloor \frac{n}{i} \right \rfloor} \right \rfloor \geq \left \lfloor \frac{n}{\frac{n}{i}} \right \rfloor = \left \lfloor i \right \rfloor = i

                                                                    \Rightarrow     i \leq \left \lfloor \frac{n}{\left \lfloor \frac{n}{i} \right \rfloor} \right \rfloor

                                                               得证: j = \left \lfloor \frac{n}{\left \lfloor \frac{n}{i} \right \rfloor} \right \rfloor

 

BZOJ1257:余数之和

\sum_{i = 1} ^{n}k\ mod\ i

给定n和k,求上面式子的值,1 <= n, k <= 10^9。

显然不能通过\theta(n)时间复杂度内算出,我们可以化简一下式子

k \ mod \ i = k - i*\left \lfloor \frac{k}{i} \right \rfloor

\sum_{i = 1} ^{n}k\ mod\ i = nk - \sum_{i=1}^{n}i*\left \lfloor \frac{k}{i} \right \rfloor

利用 \left \lfloor \frac{k}{i} \right \rfloor 在区间[l,r]为定值进行分块,在某一块中,\left \lfloor \frac{k}{i} \right \rfloor * \left [ l + (l + 1) + (l+2) +...+r \right ],然后用等差数列求和公式即可算出该区间值。时间复杂度为\theta(\sqrt{n})

#include<cstdio>
#include<iostream>
using namespace std;
typedef long long ll;
ll n, k;
int main()
{
	while(scanf("%lld %lld", &n, &k) == 2) {
		ll ans, r;
		ans = n*k;
		for(ll l = 1; l <= n; l = r+1) {
			if(k / l) r = min(n, k/(k/l));
			else r = n;
			ans -= (k/l)*(l+r)*(r-l+1)/2;
		} 
		printf("%lld\n", ans);
	}
	return 0;
}
 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值