萌新看不少题解用了欧拉函数,这里来一篇不用欧拉函数的吧QwQ(其实本质上没什么区别,但是个人认为理解和实现起来容易一些)
题目描述
求n∑i=1gcd
算法
观察题目的性质,显然每个gcd(i,n)都是n的约数。(这一点很重要,后文的约数实际上指的是i和n的最大公约数)
于是我们可以将n质因数分解:
n = p_1^{c_1} \times p_2^{c_2}\times … \times p_k^{c_k}
然后使用算术基本定理枚举c_i来枚举n的约数。
现在问题转变为求每个n的约数在gcd(i,n)中出现了多少次。
首先,一个直接的想法就是对于n的一个约数x,直接计算有多少个数是x的倍数,也就是n\div x。
但是这很容易举出反例,比如令n = 12,对于约数2,按照刚刚的想法,则有2在gcd(i,n)中出现的次数为6次,分别为2,4,6,8,10,12
但是很明显4与12的最大公因数是4而非2,6,8,12也存在类似错误情况。
于是我们考虑把错误情况踢出去。
通过观察,我们发现:如果n的约数x的y倍(下文中的y均指代这个意思)与n的最大公因数是x,需要满足一个条件:
y的质因子不包含x的次数比n的次数低的质因子。
上面的话读起来很绕口,举个例子:
还是n = 12 = 2^2 \times 3^1。对于约数2,2中次数比12质因数分解后次数低的质因子就是2和3(2中包含1个2,0个3,12中包含2个2,1个3),也就是说y中不能包含质因子2或3。
也就是上面的4(此时y = 2,包含质因子2),6(y包含3),8(y包含2),12(y包含2和3)的情况按照这个方法就都能被踢掉。
还要注意别把平次的情况去掉:
再举个例子:还是12,最大公约数是4,此时4与12的质因子2的次数都是2,所以y就可以包含2。
知道了要踢掉什么,问题就是怎么计算踢多少?
我们发现,对于x和n都含有的质因子p_i且x中的次数比n中的低,x的倍数x \times y中y不含有p_i的情况有n\div x \times (p_i - 1) \times p_i种
同时计算对答案的贡献n\div x \times (p_i - 1) \times p_i \times x也就是n \times (p_i - 1) \times p_i。
至此,本题代码就不难想了:
质因数分解n,dfs枚举每个p_i的次数,最后计算对答案的贡献。
(时间复杂度) O(\sqrt{n})
代码奉上:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const ll N = (1 << 17) + 50;
ll n,p[N],c[N],k,c2[N],ans;
void divide(ll n)//质因数分解
{
for(ll i = 2; i * i <= n; i++)
{
if(n % i == 0)
{
p[++k] = i;
while(n % i == 0)
{
n /= i;
++c[k];
}
}
}
if(n > 1)
{
p[++k] = n;
++c[k];
}
}
void dfs(ll cnt)//该枚举x中p_cnt的次数了
{
if(cnt > k)//到头了
{
ll ans2 = n;
for(ll i = 1; i <= k; i++)
{
if(c2[i] < c[i])//如果x中次数低与n中次数
{
ans2 = ans2 / p[i] * (p[i] - 1);
}
}
ans += ans2;
return;
}
for(ll i = 0; i <= c[cnt]; i++)//枚举x的次数
{
c2[cnt] = i;
dfs(cnt + 1);
}
}
int main()
{
scanf("%lld",&n);
divide(n);
dfs(1);
printf("%lld",ans);
return 0;
}
您在无意识间(可能是)使用了欧拉函数。
啊这
确实跟欧拉函数的形式很像