( 数论 )【 除法分块 】
模板题目 Gym 101615C
题目描述
对于一个数X,函数f(X)表示X所有约数的和。例如:f(6)=1+2+3+6=12。对于一个X,Smart可以很快的算出f(X)。现在的问题是,给定两个正整数L,R(L<R),Smart希望尽快地算出f(L)+f(L+1)+……+f(R)的值,你能帮助Smart算出这个值吗?
输入格式:
输入文件仅一行,两个正整数L和R(L<R),表示需要计算f(L)+f(L+1)+……+f(R)。
输出格式:
输出只有一行,为f(L)+f(L+1)+……+f(R)的值。
先提一个问题,因子7在区间[ 24, 86 ] 里出现了几次?容易的到答案为 86/7 - 23/7 次。
举个例子,当L=1, R=12 , 做出这个因子出现位置图
因子 1 2 3 4 5 6 7 8 9 10 11 12
因子1出现位置 1 1 1 1 1 1 1 1 1 1 1 1
因子2出现位置 2 2 2 2 2 2
因子3出现位置 3 3 3 3
因子4出现位置 4 4 4
因子5出现位置 5 5
因子6出现位置 6 6
因子7出现位置 7
因子8出现位置 8
............
我们要求所有因子和就是把上面的的因子出现位置个数都加起来。
我们根据上图,再做出因子出现次数图。
因子 1 2 3 4 5 6 7 8 9 10 11 12
出现次数( n/i ) 12 6 4 3 2 2 1 1 1 1 1 1
现在我们的答案ans = 1*12 + 2*6 + 3*4 + 4*3 + 5*2 + 6*2 + 7*1 + 8*1 + 9*1 + 10*1 + 11*1 + 12*1 。
显然我们就是要求 , 好好理解上面的出现次数表!!对应代码哪一部分!!
我们可以看出连续多个1, 连续多个2 ..... 我们就把出现次数相同的因子分成一整块。
这样对于出现次数为1的这一块我们用等差公式求和来算。sum += (n/left)*(right-left+1)*(left+right)/2;
对于左边界我们容易得知等于上一块的右边界加一。
我们知道了左边界,发现右边界等于 左边界因子/出现次数, right = n/(n/left);
然后,下面这个代码的本质其实就是分块求
代码:
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
ll solve( ll n ) // 求1~n的所以因子和
{
ll sum=0;
ll left,right;
for ( left=1; left<=n; left=right+1 ) { // i这个因子有多少个
right = n/(n/left); // 右边界
sum += (n/left)*(right-left+1)*(left+right)/2;
}
return sum;
}
int main()
{
ll l,r;
cin >> l >> r;
cout << solve(r)-solve(l-1) << endl;
return 0;
}
例题2: 余数求和 洛谷P2261
分析转自:https://www.luogu.org/problemnew/solution/P2261
代码:
对应上面的代码,只是把公式中的n改成了k, 再特判t是否为0. 重点理解上一题代码是如何分块的
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll k,n;
ll solve( ll n )
{
ll sum=0;
ll left,right;
for ( left=1; left<=n; left=right+1 ) {
ll t = k/left; // t可能是0, 导致程序出错, 所以需要特判
if ( t==0 ) right = n;
else right = min(k/t,n);
sum += (t)*(right-left+1)*(left+right)/2;
}
return sum;
}
int main()
{
cin >> n >> k;
cout << n*k-solve(n) << endl;
return 0;
}