数论分块之整除连加和

 

学过c++的小朋友们都知道

当有整形数字a,b,此时a/b是取整除结果的

例如a=10,b=4,此时a/b=2

现在给你一个数字N

希望你求出N/1,N/2.............N/N的总和

Format
Input
一行给出数字N

N<=1e12

Output
如题

Samples
输入数据 1
3
输出数据 1
5
Hint 3/1+3/2+3/3=3+1+1=5

 

 

 

表中同样的值会连续出现,而相同的值所划分的区间积是整除分块。

整除的性质使得从1到n的数组表可根据数值划分为不同的分块,且分块数远远小于n。

利用这种性质,我们如果能推导出每个分块具体的左右端点位置在哪,这个问题就可以快速求解出来了。

假设我们已知某一个分块的左端点L,要求解出该分块的右端点R。

设该分块的数值为K ,则k = ⌊ n/ L ⌋

由于区间[L,R]中每个数字i都满足⌊ n/ i ⌋=k

于是右边界R,即为满足条件i*K<=N中最大的i
即R=⌊ n/ k ⌋=⌊ n/ (⌊ n/ L ⌋) ⌋

例如n=20时

当L=1,求出R=1

当L=2,R=2

当L=3,R=3

.................

当L=7,R=10

#include<bits/stdc++.h>
#define int long long
using namespace std;
signed main(){
  	int n,l=1,r=0,ans=0;
  	cin>>n;
 	while(l<=n){
    	r=n/(n/l);
    	ans+=(r-l+1)*(n/l);
    	l=r+1;
  	}
  	cout<<ans;
}

  

 

给出正整数n和k,计算j(n, k)=k mod 1 + k mod 2 + k mod 3 + … + k mod n的值
其中k mod i表示k除以i的余数。
例如j(5, 3)=3 mod 1 + 3 mod 2 + 3 mod 3 + 3 mod 4 + 3 mod 5=0+1+0+3+3=7
Input

输入仅一行,包含两个整数n, k。
1<=n ,k<=10^9
Output

输出仅一行,即j(n, k)。

Sample Input

5 3
Sample Output

7

 

 

 .

#include <cstdio>
long long n, m, ans;
long long min(long long x, long long y) {
    return x < y ? x : y;
}
int main() {
    scanf("%lld %lld", &n, &m), ans = n * m;
    for (int i = 1, j; i <= n; i = j + 1)
    {
        if (m/i)
            j=min(m / (m / i), n);
        else
            j=n;
         
        ans -= (m / i) * (i + j) * (j - i + 1) / 2;
    }
    printf("%lld\n", ans);
    return 0;
}

  

k%i=k-trunc(k/i)*i,当n>k时,直接把长度为(n-k)的区间长度乘以k加入答案,
然后就处理i≤k的时候就可以了,然后就用j=n/(n/i)直接跳到(k/i都相等)区间末尾,
然后需要对这个区间的等差数列求和加入答案,输出即可。。
注意点:输出%lld,加入答案的时候强制类型转换为long long。。

精髓点:
1:j=n/(n/i)
2:已知区间始末和trunc(k/i)时,求k%i的等差数列的和的公式:
∑=(j-i+1)k-(j-i+1)(i+j)/2*(k/i);

#include<cstdio>
#include<iostream>
using namespace std;
int n,k,i,j;
long long ans=0;
int main()
{
    scanf("%d %d",&n,&k);
    if (n>k)
      {
        ans+=(long long)(n-k)*k;
        n=k;
      }
    j=0;
    for (i=1;i<=n;i=j+1)
      {
        j=k/(k/i);
        if (j>=n)
          j=n;
        cout<<"j is   "<<j<<"i is   "<<i;
        system("pause");
        ans+=(long long)(j-i+1)*k-(long long)(j-i+1)*(i+j)/2*(k/i);
//i代表这一段数的开始位置,j是结束位置.则这一段取mod的结果,
//利用公式k%i=k-trunc(k/i)*i即等于这一段数字的个数*k,
//再减去这一段用k/i的结果(i是首项,j是尾项,j-i+1是项数,利用等差数列进行求和)
      }
    printf("%lld",ans);
    return 0;
}

12 9
j is   1 i is   1请按任意键继续. . .9/1=9..这一段只有一个数
j is   2 i is   2请按任意键继续. . .9/2=4..这一段也只有一个数
j is   3 i is   3请按任意键继续. . .9/3=3..这一段也只有一个数
j is   4 i is   4请按任意键继续. . .9/4=2..这一段也只有一个数
j is   9 i is   5请按任意键继续. . .9/5=1,包括其后的9/6=1,9/7=1,9/8=1,9/9=1.这一段有5个数
39

  

 

求约数个数之五
Description
对于输入一个数字,输出其约数个数。

例如10就有4个约数1,2,5,10 。

这种问题,小J已做得非常熟练了

现在老师将这个问题变了下

给出一个数字N,求1到N之间每个数字的约数个数,再输出其累加和

Format
Input
一个数字N,N< =1e14

Output
如题

Samples
输入数据 1
3
输出数据 1
5

 

 

 

#include<bits/stdc++.h>
using namespace std;
long long solve(long long x){
    long long L = 1,R = 0;
    long long res = 0;
    while(L <= x){
        R = x/(x/L);
        res += (x / L) * (R - L + 1);
        L = R + 1;
    }
    return res;
}

int main()
{
    long long n;
	cin >> n;
    cout << solve(n);
    return 0;
}

 

另一个使用容斥定理求解的程序

Sol:假设n=10,先枚举较小的约数i,i<=sqrt(n)

例如当i=1时,区间[1..10]之内会有10个数字是1的倍数,不妨统设为x,则可以得到下面的式子

 

当i=2时,区间[1..10]之内会有5个数字是2的倍数,不妨统设为x,则可以得到下面的式子

 

 当i=3时,区间[1..10]之内会有3个数字是3的倍数,不妨统设为x,则可以得到下面的式子

 

于是上面这三个表,就一共有18个形如i*j=x的表达式,在表达式的左边出现了36个数字,它们都为[1..10]之间某个数字的约数

但上表中有些表达式是多余的。

例如

1*1=1

2*2=2

3*3=3

这三个式中的i与j值是一样的,只能算一个,所以另一个要去掉,一共去掉3个,其值为int(sqrt(n))

再例如

2*1=2

3*1=3

3*2=6

这三个式子在整个的18个表达式中,是出现了2次的。

所以这三个式子中的i,j都要去掉。一共去掉6个

用数学公式来表达就是

 

 

 

 于是所有要去掉的约数个数=int(sqrt(N))+int(sqrt(N))*int(sqrt(N))-int(sqrt(N))=int(sqrt(N))*int(sqrt(N))

 

.

 

 

 

#include<bits/stdc++.h>
using namespace std;
int main()
{
	long long n,sum=0;
	cin>>n;
	for(int i=1;i<=n/i;i++)
		sum=sum+n/i;
	cout<<sum*2-(long long)sqrt(n)*(long long)sqrt(n)<<endl;
	return 0;
}

  

 

如果使用欧拉筛法来做,时间复杂度为O(N)

#include <iostream>
using namespace std;
const int n = 1e7;
const int N = n + 1;
int m=0;
int a[N], b[N], c[N], d[N];
int f[N], g[N];
void init()
{
    f[1] = g[1] = 1;
    for (int i = 2; i <= n; i++)
    {
        if (!a[i])
        //如果i不是质数的话,加入质数队列
        {
            b[++m] = i;
            //队列中第m个质数为i
            c[i] = 1, f[i] = 2;
        }
        for (int j = 1; j <=m && b[j] * i <= n; j++)
        //枚举质数队列,从小到大
        {
            int k = b[j];
            a[i * k] = 1;
            //标记i*k不是质数
            if (i % k == 0)
            //如果k是i最小的质因子的话
            {
                c[i * k] = c[i] + 1;
                //指数增加1
                f[i * k] = (f[i] / (c[i]+1))* (c[i * k] + 1);
                //约数个数变化,详见图1
              
                break;
            }
            else
            {
                c[i * k] = 1;
                //k做为i*k的最小质因子,现在出现了1次
                f[i * k] = 2 * f[i];
                //约数个数发生变化
            }
        }
    }
}
  
int main() 
{
   init();
    int x;
    cin>>x;
    int ans=0;
    for (int i=1;i<=x;i++)
         ans=ans+f[i]; 
    cout<<ans<<endl;
    return 0;
}

  

 

 

 

 

 

求约数之和4



给出一个数字N,求其约数之和是个比较简单的问题

例如N=6时,其约数之和为1+2+3+6=12.

现在给你两个数字a,b

求a,b之间所有数字的约数之和,再累加求和
Format
Input

一行给出a,b

a<=b<=2e9
Output

如题
Samples
输入数据 1

2 4

输出数据 1

14

 

Sol:明显对于1到N之间的数字,其约数范围也为1到N这个范围内。

于是我们可以枚举约数L,于是N/L就代表1到N之间有多少个数字是L的倍数,L也必为它们的约数。

例如当L=7时,N=20时

N/20=2,说明1到20以内有两个数字是7的倍数,易知为7,14,也就是说在算7和14的约数之和时,必然要将7统计进去。

然后这个算法高明的地方在于

当L=8,9,10时,N/L=2

于是这一段的L=7,R=10

于是这一段的约数之和为2*7+2*8+2*9+2*10=2*(7+8+9+10)=2*(7+10)*(10-7+1)/2

 

下图中最后一行公式有误 。


 

 

#include<bits/stdc++.h>
using namespace std;
long long solve(long long n)
{
    long long L = 1,R = 0;
    long long res = 0;
    while(L <= n){
        R = n/(n/L);
        res += n / L * (L + R) * (R - L + 1) / 2;
        L = R + 1;
    }
    return res;
}

int main()
{
    long long a,b;cin >> a >> b;
    cout << solve(b) - solve(a-1);
    return 0;
}

 

 

posted @ 2022-12-25 21:39  我微笑不代表我快乐  阅读(6)  评论(0编辑  收藏  举报