1. 程式人生 > >約數和(神奇的數列優化)

約數和(神奇的數列優化)

main urn sca 結果 using 思路 告訴 inline 數字

題目大意

求區間[x,y]中所有元素的因數和(x<=y)

思路

如果這道題太難了,來看看另一道簡單的題:

給你很多個數N,需要你算出這些數所有約數的和。(N的約數指能整除N的正整數),例如12的約數有1,2,3,4,6,12。所以約數和為1+2+3+4+6+12=28。

(1)50分的代碼,打表實現

技術分享圖片
#include<cstdio>
const int maxn=5000010;
int t,n,s[maxn];
int main(){
    for(int i=1;i<=maxn;i++)
    for(int j=i;j<=maxn;j+=i)
    s[j]
+=i; scanf("%d",&t); while(t--){ scanf("%d",&n); printf("%d\n",s[n]); } return 0; }
View Code

(2)枚舉+卡常數+記憶化

技術分享圖片
#include<bits/stdc++.h>
using namespace std;
inline const int read(){
    register int x=0,f=1;
    register char ch=getchar();
    while(ch<0
||ch>9){if(ch==-)f=-1;ch=getchar();} while(ch>=0&&ch<=9){x=(x<<3)+(x<<1)+ch-0;ch=getchar();} return x*f; } int T,v,f[100000010]; inline int go(){ int i,sum=0; for(i=1;i*i<v;i++) if(v%i==0) sum+=i+v/i; if(i*i==v) sum+=i; return sum; } int main(){ T
=read(); while(T--){ v=read(); if(!f[v]) f[v]=go(); printf("%d\n",f[v]); } return 0; }
View Code

雖然並沒有什麽卵用

但我們可以冷靜下來想一想

既然本題求的是區間約數和,我們首先想到的方法就是用前綴和的思想,ans [x~y]=ans[1~y] – ans[1~x-1]

求1~n的約數和,用的是這種方法:

1:1/2=0 sum=1

2:2/2=1 2/3=0 sum=1*2+2=4

3:3/2=1 3/3=1 3/4=0 sum=1*3+1*2+3=8

4:4/2=2 4/3=1 4/4=1 sum=2*2+1*3+1*4+4=15

它的正確性很好證明

n/a的值就是1~n這些數中以a為因數的數的個數,再乘以a即因數和

最後加上n是由於n本身是n的因數

所以Σ(i=1,n)n/i*i就是結果

然後我們就有了60分的代碼(why,也許你想問)

技術分享圖片
#include<iostream>
using namespace std;
long long l,r,al,ar;
long long work(long long a){
    long long result=0;
    for(long long i=2;i<=a;i++){
        if(a/i==0)break;
        result+=(a/i)*i;
    }
    result+=a;
    return result;
}
int main(){
    cin>>l>>r;
    al=work(l-1);
    ar=work(r);
    cout<<ar-al;
}
View Code

很明顯這種方法會超時呢(美麗的TLE)

但其實這題思路是沒毛病的,但是TLE的問題說明代碼需要優化加速(怎麽加,你告訴我)

用等差數列優化

首先根據之前的發現,以4為例,會發現4/3=1,4/4=1,到待除的除數較大時以a為因數的數的個數會呈現很長一段的相等局面,同樣的,在別的數字上也會出現類似的情況,而且數字越大,這種情況的出現越多,我把3,4分別叫做本例中情況的左右邊界。而且不難發現這種情況的連續序列中的a都是等差的,差為一,所以可以直接利用等差數列求和(這麽巧!!!)

就是下面這個弱智的小學生公式

技術分享圖片

所以我們只需要枚舉一下左右邊界即可

代碼就不給了啊

約數和(神奇的數列優化)