除法分块

2020-02-08
2分钟阅读时长

背景

定义一个函数f,f(x)表示x的所有因子和,比如f(6)=12

对于求单个的f(x)很简单,但是如果求sum(f(i)) i in [a, b]呢?

朴素做法

最暴力的做法很容易就想到

ll sum = 0;
for(int i=a; i<=b; i++)
{
    for(int j=1; j*j<=i; j++)
    {
        if(i%j==0)
            sum += j;
        if(j*j != i)
            sum += j/i;
    }
}

这种做法实在是太慢了,时间复杂度是O(n*sqrt(n))

线性做法

对于一个因子L,在1~n这个范围里面,包含L这个因子的数字只有n/L(除法向下取整)这么多个

且1~n里面,最大的因子是n,所以我们枚举一下因子,每次累加L*n/L即可

int cal(int n)
{
    int sum = 0;
    for(int i=1; i<=n; i++)
    {
        sum += i*(n/i);
    }
    return sum;
}

cout<<cal(a) - cal(b-1)<<endl;

除法分块

当n非常大的时候,即便是线性做法也是不够快的,我们考虑求sum(f(i)) i in[1, 12]

根据上面说道,每个因子的出现次数为n/L,所以对于1~12这个范围来说,1~12因子出现的次数为[12, 6, 4, 3, 2, 2, 1, 1, 1, 1, 1, 1]

我们注意到有连续的因子区间出现的次数是一样的,那么我们可不可以将出现次数相同的因子一起算呢?

我们只需要知道区间的左右端点就可以做到了

左端点l是容易的求的,左端点等于上一个右端点+1,左端点初始化为1

右端点呢?右端点为n/(n/l)(除法皆为向下取整),为什么右端点是这样的?当然是打表找规律啦看看这篇证明

所以我们很容易就能求得1~n的每个数字的因子和

ll cal(ll n)
{
    ll sum = 0;
    for(ll L=1, R; L<=n; L=R+1)
    {
        // 区间右端点
        R = n/(n/L);
        // n/L为这段区间每个因子的出现次数
        // 我们知道连续的因子区间构成了一个d=1的等差数列,所以这一段区间的因子和为
        // (L+R)*(R-L+1)/2
        // (首项+末项)*(区间长度)/2
        sum += (n/L) * (L+R)*(R-L+1)/2;
    }
    return sum;
}

// 求i in [a, b]的sum(f(i))
ll a, b;
cin>>a>>b;
cout<<cal(b) - cal(a-1)<<endl;

例题

codeforces gym101652 Fear Factoring

1

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define IO ios::sync_with_stdio(0)
typedef pair<int, int> P;
const int maxn = 2e5+5;
const ll mod = 1e9+7;
using namespace std;
 
 
ull cal(ull n)
{
	ull ans = 0;
	for(ull l=1, r; l<=n; l=r+1)
	{
		r = n/(n/l);
		ans += (n/l) * (l+r)*(r-l+1)/2;
	}
	return ans;
}
int main()
{
	IO;
	ull a, b;
	cin>>a>>b;
	cout<<cal(b) - cal(a-1)<<"\n";
	return 0;
}
上一页 扩展KMP
下一页 KMP