約數和(神奇的數列優化)
題目大意
求區間[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]View Code+=i; scanf("%d",&t); while(t--){ scanf("%d",&n); printf("%d\n",s[n]); } return 0; }
(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‘View Code||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; }
雖然並沒有什麽卵用
但我們可以冷靜下來想一想
既然本題求的是區間約數和,我們首先想到的方法就是用前綴和的思想,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都是等差的,差為一,所以可以直接利用等差數列求和(這麽巧!!!)
就是下面這個弱智的小學生公式
所以我們只需要枚舉一下左右邊界即可
代碼就不給了啊
約數和(神奇的數列優化)