\(Description\)
求\(\sum\limits_{i=1}^n\sum\limits_{j=1}^n gcd(i,j)\)
\(Solution\)
這種\(gcd\)計數的題一般思想是枚舉\(gcd\)。
對於這道題,有一下幾種做法,循序漸進
暴力:\(O(n^2logn)\)
就是暴力枚舉所有數求\(gcd\),期望得分不清楚,大概\(20pts\)
可以優化\(gcd\)函數,記憶化一下。
畫個矩陣發現可以只求下三角,即只求\(\sum\limits_{i=1}^n\sum\limits_{j=1}^{j<i}gcd(i,j)\),將答案\(*2\)后再單獨用等差數列求和公式處理對角線上的情況\((i==j)\),一頓操作猛如虎,但因為會\(MLE\)仍然無法通過\(70pts\)
離正解只差一步的暴力:\(O(n\sqrt n)\)
枚舉所有數\(i\),設組成數對的另一個數為\(j\),仍是只考慮\(i<j\)的情況。設\(gcd(i,j)=k\),則\(gcd(\frac{i}{k} ,\frac{j}{k})=1\),符合這樣的數\(\frac{j}{k}\)的個數就應該是\(\phi(\frac{i}{k})\),所以\(O(\sqrt n)\)枚舉\(i\)的所有因數為\(gcd\),\(ans+=gcd\cdot \phi(\frac{i}{gcd})\)。總復雜度\(O(n\sqrt n)\)。
\(70pts\)肯定是穩過的,至此離正解只差一步。
正解\(O(nln n)\)
上面枚舉因子\(O(\sqrt n)\)顯然是可以優化的,可以先枚舉\(gcd\),再枚舉另一個因子得出\(i=gcd*x\),和上面方法是等效的,調和級數\(O(ln n)\)總復雜度\(O(n ln n)\)
\(Code\)
int n,prime[maxn],pcnt,is_not_prime[maxn];
int phi[maxn];
ll ans;
void Get_Phi(int x)
{
is_not_prime[1]=1;
for(re int i=2;i<=x;++i)
{
if(!is_not_prime[i]) prime[++pcnt]=i,phi[i]=i-1;
for(re int j=1;j<=pcnt;++j)
{
if(i*prime[j]>x) break;
is_not_prime[i*prime[j]]=1;
if(!(i%prime[j]))
{
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
else phi[i*prime[j]]=phi[i]*phi[prime[j]];
}
}
}
int vis[maxn];
int main()
{
n=read();
Get_Phi(n);
for(re int i=1;i<=n;++i)//枚舉gcd
for(re int j=2;j*i<=n;++j)//枚舉數對中較大的數/gcd的結果,從2枚舉是為了不考慮對角線
{
ans+=(ll)phi[j]*i;
}
ans*=2;//上三角下三角
ans+=(ll)(1+n)*n/2;//統計對角線
printf("%lld\n",ans);
return 0;
}
\(Updata\)
當我在\(luogu\)刷這道題的\(k\)倍經驗時,遇到了一道多組數據且\(T<=200000\)的題,於是成功\(TLE\)了
其實只需要對上面算法進行小的改動就行
ans+=(ll)phi[j]*i;
注意到這一行\(ans\)更新的其實是\(\sum\limits_{x=1}^{x<i*j}gcd(x,i*j)\),相當於對\(i*j\)的答案更新
那么可以這樣寫
f[i*j]+=(ll)phi[j]*i;
每個答案就是\(\sum\limits_{i=1}^nf[i]\)
所以對於數據個數很多的情況,預處理到最大范圍,求前綴和,\(O(1)\)回答即可。