bzoj 4804 尤拉心算
題面:
一句話題意:
給出 \(k\) 組詢問,每次給你一個 \(n\) ,讓你回答 \(\displaystyle\sum_{i=1}^{n}\sum_{j=1}^{n}\phi(gcd(i,j)\)
題解
下面,又到了我們的頹柿子時間啦。
我們要求的是這個柿子:
\(\displaystyle\sum_{i=1}^{n}\sum_{j=1}^{n}\phi(gcd(i,j)\)
先列舉一個 \(d\) 可得:
\(\displaystyle\sum_{d=1}^{n}\phi(d) \sum_{i=1}^{n}\sum_{j=1}^{n} [gcd(i,j) == d]\)
後面的那兩個求和柿子可以提出一個 \(d\)
\(\displaystyle\sum_{d=1}^{n} \phi(d) \sum_{i=1}^{n\over d}\sum_{j=1}^{n\over d} [gcd(i,j) == 1]\)
後面的柿子有沒有覺得有點熟悉?
如果你做過 儀仗隊 ,你就會一眼把他秒了。
我們有一個等式: \(\displaystyle\sum_{i=1}^{n}\sum_{j=1}^{n}[gcd(i,j) == 1] = \sum_{i=1}^{n}2 \times \phi(i) - 1\)
具體證明:
首先對於一個 \(i\), 與他互質的個數 是 \(\phi(i)\) ,又因為 (i,j) 和 (j, i)這兩個數對算兩個,所以要乘二,減一則是要減去算重複的1.
對於 \(i \leq j\) 的情況有 \(\displaystyle\sum_{i=1}^{n} \sum_{j=1}^{i}[gcd(i,j) == 1] = \sum_{i=1}^{n} \phi(i)\)
對於 \(i >= j\) 的情況同理,最後在減去重複計算的 1
把這個等式代回原來的柿子,可以化簡為:
\(\displaystyle\sum_{d=1}^{n}\phi(d) \sum_{i=1}^{n \over d} 2 \times \phi(i) - 1\)
他還可以化為
\(\displaystyle(\sum_{d=1}^{n} \phi(d) (2 \times sum[{n \over d}])) - sum[n])\)
對後面的求和柿子,可以預處理出來 \(\phi\) 函式的字首和。,在用一下數論分塊,列舉每個 \(d\) 即可。
Code
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define LL long long
const int N = 1e7+10;
int t,n,m,tot;
int prime[N],phi[N];
LL sum[N];
bool check[N];
inline int read()
{
int s = 0,w = 1; char ch;
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s =s * 10+ch - '0'; ch = getchar();}
return s * w;
}
void YYCH()
{
phi[1] = 1;
for(int i = 2; i <= N-5; i++)//預處理φ函式值
{
if(!check[i])
{
prime[++tot] = i;
phi[i] = i-1;
}
for(int j = 1; j <= tot && i * prime[j] <= N-5; j++)
{
check[i * prime[j]] = 1;
if(i % prime[j] == 0)
{
phi[i * prime[j]] = phi[i] * prime[j];
}
else
{
phi[i * prime[j]] = phi[i] * phi[prime[j]];
}
}
}
for(int i = 1; i <= N-5; i++)//求字首和
{
sum[i] = sum[i-1] + phi[i];
}
}
LL slove(int n)
{
LL res = 0;
for(int l = 1,r; l <= n; l = r + 1)//數論分塊
{
r = n/(n/l);
res += 1LL * 2 * (sum[n/l]) * (sum[r]-sum[l-1]);
}
res -= sum[n];
return res;
}
int main()
{
t = read(); YYCH();
while(t--)
{
n = read();
printf("%lld\n",slove(n));
}
return 0;
}