这篇博客将会介绍一道7倍经验的欧拉函数题。
update:欧拉函数的求法
根据式子求:
int phi(int n) {
int ret = n;
for (int i = 1; prime[i] * prime[i] <= n; i++) {
if (n % prime[i] == 0) {
ret /= prime[i];
ret *= (prime[i] - 1);
while (n % prime[i] == 0) n /= prime[i];
}
}
if (n > 1) {
ret /= n;
ret *= (n - 1);
}
return ret;
}
埃式筛法:
void init() {
for (int i = 1; i <= MAX; i++) {
phi[i] = i;
}
for (int i = 2; i <= MAX; i++) {
if (phi[i] == i) {
for (int j = i; j <= MAX; j += i) {
phi[j] /= i;
phi[j] *= (i - 1);
}
phi[i] = i - 1;
}
}
}
根据欧拉函数的性质可以得出其线性筛的代码:
void init() {
phi[1] = 1;
for (int i = 2; i <= MAX; i++) {
if (!mark[i]) {
prime[++m] = i;
phi[i] = i - 1;
}
for (int j = 1; j <= m && prime[j] * i <= MAX; j++) {
phi[i * prime[j]] = phi[i] * (i % prime[j] ? (prime[j] - 1) : prime[j]);
mark[i * prime[j]] = 1;
if (i % prime[j] == 0) break;
}
}
}
基础题
这应该是这7道题中最水的一道,因为它的范围只有 (1 < N < 501)。
如果你会欧几里得算法你应该就能A这道题,暴力枚举两个数,求其 (gcd),时间复杂度 (O(TN^2log{N}))。
这个暴力的代码我就不给了。
这题题面说的有点迷,其实和上一题求的是一个东西。只不过数据范围变大了且没有了多组数据。
对要求的东西进行分析:设 (f(n)=sumlimits_{i=1}^{n-1}gcd(i,n)),则题目要求即为 (sumlimits_{i=2}^{n}f(i))。
发现如果 (gcd(i,j)=1),则 (gcd(ik,jk)=k)。
我们可以枚举最大公约数 (k),然后(f(n)=sumlimits_{k|n}k imes phi(k))((phi)是欧拉函数)。
通过提前将欧拉函数筛出来可以(O(1))查询欧拉函数,但这样做的复杂度是(O(Nsqrt{N})),还是太慢。
我们考虑枚举公约数的时候把所有是其倍数的(n)都算出来,这样的(n)其实就是(k,2k,3k, cdots, floor(frac{N}{k}) imes k),答案会加上(k imes sumlimits_{i=1}^{floor(frac{N}{k})}phi(i)),所以我们求一个欧拉函数的前缀和,这样对于每一个(gcd:k)就能做到(O(1))了,总复杂度(O(N))。
注意这道题((a,a))这种不算,所以在计算时(phi(1))不算,要减掉。
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 2000010;
long long prime[N], phi[N], tot;
long long sum[N];
bool mark[N];
long long n;
long long ans;
void get_phi() {
phi[1] = sum[1] = 1;
for (int i = 2; i <= n; i++) {
if (!mark[i]) {
prime[++tot] = i;
phi[i] = i - 1;
}
for (int j = 1; j <= tot; j++) {
if (i * prime[j] > n) break;
mark[i * prime[j]] = 1;
phi[i * prime[j]] = phi[i] * ((i % prime[j] == 0) ? prime[j] : (prime[j] - 1));
if (i % prime[j] == 0) break;
}
sum[i] = sum[i - 1] + phi[i];
}
}
int main() {
cin >> n;
get_phi();
for (int i = 1; i <= n; i++) {
ans += (sum[n / i] - 1) * i;
}
cout << ans;
return 0;
}
这道题多了个多组数据,如果还按上述做法做的话复杂度是(O(TN))会炸。
考虑提前预处理答案,做到(O(1))查询。
根据上面的推到,可以设计一个类似埃式筛法的算法,枚举所有公约数(i),把他的倍数(j)的答案都加上(i imes Phi(frac{j}{i})),当然(j eq k)。
这只是算出了(f(n)),还要做一遍前缀和就是题目所求。
这个算法的复杂度是(O(Nlog{N}+T))的,在解决多组数据的问题时胜过上述算法。
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 1000010;
long long prime[N], phi[N], ans[N], tot;
bool mark[N];
int n;
void get_ans() {
phi[1] = 1;
for (int i = 2; i <= 1000000; i++) {
if (!mark[i]) {
prime[++tot] = i;
phi[i] = i - 1;
}
for (int j = 1; j <= tot; j++) {
if (prime[j] * i > 1000000) break;
mark[i * prime[j]] = 1;
if (i % prime[j] == 0) {
phi[i * prime[j]] = phi[i] * prime[j];
break;
} else {
phi[i * prime[j]] = phi[i] * (prime[j] - 1);
}
}
}
for (int i = 1; i <= 1000000; i++) {
for (int j = i + i; j <= 1000000; j += i) {
ans[j] += i * (phi[j / i]);
}
}
for (int i = 1; i <= 1000000; i++) {
ans[i] += ans[i - 1];
}
}
int main() {
get_ans();
while (scanf("%d", &n) != EOF && n) {
printf("%lld
", ans[n]);
}
return 0;
}
UVA11426 拿行李(极限版) GCD - Extreme (II)
这两题和前一题基本一样,数据范围并没有太大的变化,用前面的代码就可以A。
变式
这题乘个(2)再把((a,a))这种情况加上就行了。
这题把枚举所有约数变成枚举所有质数,求和变成求个数即可。
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 10000010;
int prime[N], phi[N], tot;
long long sum[N];
bool mark[N];
int n;
long long ans;
void get_phi() {
phi[1] = sum[1] = 1;
for (int i = 2; i <= n; i++) {
if (!mark[i]) {
prime[++tot] = i;
phi[i] = i - 1;
}
for (int j = 1; j <= tot; j++) {
if (i * prime[j] > n) break;
mark[i * prime[j]] = 1;
phi[i * prime[j]] = phi[i] * ((i % prime[j] == 0) ? prime[j] : (prime[j] - 1));
if (i % prime[j] == 0) break;
}
sum[i] = sum[i - 1] + phi[i];
}
}
int main() {
cin >> n;
get_phi();
for (int i = 1; i <= tot; i++) {
ans += 2 * sum[n / prime[i]] - 1;
}
cout << ans;
return 0;
}
❀❀完结撒花❀❀