又是一道非常神奇的數論題,學到了很多又花了一晚上QWQ
【題目地址】
題目大意
多組詢問T≤50000,每次給定一個n≤106,詢問下面式子在mod 109+7意義下的值:
i=1∑nj=1∑nmax(i,j)×σ1(ij)
其中σ1(i)表示i的約數之和。
這次居然連max都跑進去了,emmmm…
我們還是先把不好看的max消除掉,所以對於每個i,我們列舉比它小的j,那麼max(i,j)=i,由於(i,j),(j,i)算兩次,但是(i,i)算一次,所以原式轉化為:
2×(i=1∑nj=1∑ii×σ1(ij))−i=1∑niσ1(i2)
我們把它分成兩部分分析:
一個是前半部分:
i=1∑nj=1∑ii×σ1(ij)
根據這個題的經驗,我們顯然可以將後面的那一個σ1(ij)變換掉:
i=1∑nj=1∑ii×σ1(ij)=i=1∑nj=1∑iix∣i∑y∣j∑[gcd(x,y)=1]yxj
然後大都是這個題的套路,我們作如下變換:
i=1∑nj=1∑ii×σ1(ij)======i=1∑nj=1∑iix∣i∑y∣j∑[gcd(x,y)=1]yxji=1∑nij=1∑ix∣i∑y∣j∑yxjw∣x,w∣y∑μ(w)w=1∑nμ(w)w∣x∑xx∣i∑iw∣y∑y∣j∑yjw=1∑nμ(w)x=1∑⌊wn⌋xwx∣i∑iwy=1∑⌊wn⌋y∣j∑ywjww=1∑nμ(w)w2x=1∑⌊wn⌋xx∣i∑iy=1∑⌊wn⌋y∣j∑yjw=1∑nμ(w)w2i=1∑⌊wn⌋ix∣i∑xj=1∑⌊wn⌋y∣j∑yj
因為∑y∣jyj=∑y∣jy,而根據在這個題目的技巧,我們知道σ1(j)=∑y∣jy,所以上面繼續可以變換為:
w=1∑nμ(w)w2i=1∑⌊wn⌋ix∣i∑xj=1∑⌊wn⌋y∣j∑yj=w=1∑nμ(w)w2i=1∑⌊wn⌋iσ1(i)j=1∑⌊wn⌋σ1(j)
然後我們可以O(n)的篩出σ1,μ,並預處理上面的所用到的字首和。
一個是後半部分:
i=1∑niσ1(i2)
這個處理就和上面的十分類似了:
i=1∑niσ1(i2)=======i=1∑nix∣i∑y∣i∑[gcd(x,y)=1]yixi=1∑nix∣i∑y∣i∑yixw∣x,w∣y∑μ(w)w=1∑nμ(w)w∣i∑iw∣x,x∣i∑xw∣y,y∣i∑yiw=1∑nμ(w)i=1∑⌊wn⌋iwx∣i∑xwy∣i∑ywiww=1∑nμ(w)w2i=1∑⌊wn⌋ix∣i∑xy∣i∑yiw=1∑nμ(w)w2i=1∑⌊wn⌋iσ1(i)σ1(i)w=1∑nμ(w)w2i=1∑⌊wn⌋iσ12(i)
此時,我們可以同樣的預處理,然後帶入原式,每次數論分塊,然後O(n)的回答,複雜度大概O(n+Tn),但是最大資料有106+2×50000×1000,差不多一億左右,而且加上大常數,最大資料大概跑3s左右,雖然喪心病狂地卡卡常就能過,但是肯定還有更優秀的做法。
我們將上面的兩個式子帶回原式,作如下變換:
ans(n)===2×⎝⎛w=1∑nμ(w)w2i=1∑⌊wn⌋iσ1(i)j=1∑⌊wn⌋σ1(j)⎠⎞−⎝⎛w=1∑nμ(w)w2i=1∑⌊wn⌋iσ12(i)⎠⎞w=1∑nμ(w)w2⎝⎛i=1∑⌊wn⌋(2⋅iσ1(i)−iσ12(i))j=1∑⌊wn⌋σ1(j)⎠⎞w=1∑nμ(w)w2⎝⎛i=1∑⌊wn⌋iσ1(i)⎝⎛2⋅j=1∑⌊wn⌋σ1(j)−σ1(i)⎠⎞⎠⎞
如果我們能預處理處所有的ans(n),然後每次O(1)的回答,那就會優秀的多,所以我們發現ans(n)不好直接求,所以考慮一個神奇的東西:差分
其實這個技巧在前面做過的題目中出現過:Imagine大佬-Orz,Imagine這個題目就用的差分做的。
我們來看,差分後如何計算:
cans(n)=ans(n)−ans(n−1)
直接算,發現並不好算QAQ,所以我們考慮對於一個單個的n,那麼定義它的貢獻就為:
=d∣n∑μ(d)d2⎝⎛⎝⎛2⋅i=1∑⌊dn⌋σ1(i)−σ1(⌊dn⌋)⎠⎞⋅⌊dn⌋⋅σ1(⌊dn⌋)⎠⎞
其實這裡的意思,對於每一個cans(n),對其進行貢獻的有它的因子d,然後帶入上面的式子可以得到這個。
而ans(n),就是cans(1∼n)所有的貢獻,所以我們此時再求出cans的字首和就是ans了。
也就是我們將一個字首和中的一些項拆了出來,而兩個字首和之間差的就是n的貢獻,所以差分的cans就等於這個。
而上面的那個cans的預處理,我們用類似埃氏篩,列舉因子倍數O(nlogn)的預處理即可。
所以總的複雜度變成了O(nlogn+T),就不卡常啦!程式碼還是比較好寫。
51Nod官網上還有一種不同的題解,方法差不多但是又有很多不同的技巧,有興趣的可以去官網看。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int M=1e6+10,N=1e5+10,MAX=1e6+1;
const ll Mod=1e9+7;
ll prime[N],sigma[M],mu[M],c[M],cnt,ans[M];
bool vis[M];
void init(){
mu[1]=sigma[1]=1;
for(ll i=2;i<MAX;i++){
if(!vis[i]){
prime[++cnt]=i;
mu[i]=-1;
sigma[i]=1+i;
c[i]=1+i;
}
for(ll j=1,v;j<=cnt&&i*prime[j]<MAX;j++){
v=i*prime[j];
vis[v]=1;
if(!(i%prime[j])){
c[v]=c[i]*prime[j]+1;
sigma[v]=sigma[i]/c[i]*c[v];
break;
}
mu[v]=-mu[i];
sigma[v]=sigma[i]*sigma[prime[j]];
c[v]=prime[j]+1;
}
}
ll sum=1;
for(ll i=2;i<MAX;i++){
sum=(sum+sigma[i])%Mod;
sigma[i]=(i*sigma[i]%Mod*((2ll*sum-sigma[i])%Mod)%Mod)%Mod;
if(sigma[i]<0)sigma[i]+=Mod;
}
for(ll i=1,v;i<MAX;i++){
v=((i*i)%Mod*mu[i])%Mod;
if(v<0)v+=Mod;
for(ll j=1;i*j<MAX;j++){
ans[i*j]=(ans[i*j]+v*sigma[j]%Mod)%Mod;
}
}
for(ll i=2;i<MAX;i++)ans[i]=(ans[i-1]+ans[i])%Mod;
}
int T,n;
int main(){
init();
scanf("%d",&T);
for(int cas=1;cas<=T;cas++){
scanf("%d",&n);
printf("Case #%d: %lld\n",cas,ans[n]);
}
return 0;
}