[学习笔记] 乘性函数 - 数论
我们要求 \(\sum\limits_{i=1}^n \gcd(i, n)\),但 \(gcd\) 没啥卵用,所以尝试给这n个正整数分组。对于 \(gcd(i,n)=1\) 的数给他们归到 \(G(1)\) 这个集合里去,当然,这个集合元素的数量为 \(\phi (n)\)。对于 \(gcd(i,n)=2\) 的数归到 \(G(2)\) 这个集合里去。观察 \(G(2)\) 这个集合可以发现,如果把所有元素都除以2,那么他们都会和 \(\frac{n}{2}\) 互素。那么 \(G(2)\) 这个集合的元素数量即为 \(\phi (\frac{n}{2})\)……找到规律,于是,问题就转化为了:\(\sum\limits_{d\mid n}d*\phi (\frac{n}{d})\)。
又因为 \(n\) 和 \(\frac{n}{d}\) 是一一对应的,所以也可以写成 \(\sum\limits_{d\mid n}\frac{n}{d}*\phi (d)\)。
考虑分解欧拉函数 \(\phi (d) = d(1-\frac{1}{p_1})(1-\frac{1}{p_2})…(1-\frac{1}{p_i})\)
观察上式,我们知道,\(n=p_1^{k_1}p_2^{k_2}…p_i^{k_i}\),并且 \(n\) 的因子 \(d\) 其实是对 \(n\) 所有素因子的排列组合。令 \(t_i=\frac{p_i-1}{p_i}\)。当 \(d=p_i^{k_i}\) 时,无论 \(k_i\) 为多少(不等于0),\(\prod\limits_{p_i\mid d}\frac{p_i-1}{p_i}=t_i\)。而且,这样的 \(d\) 一共有 \(k_i\) 个,所以能够给答案的贡献为 \(k_i\times t_i\)。同理,当 \(d=p_i^{k_i}p_j^{k_j}\) 时,对答案产生的贡献为 \(k_ik_j\times t_it_j\)。特殊地,当 \(d=p_i^0\) 时贡献为0。枚举所有 \(d\),所有的贡献加在一起即为:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n, ans;
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n;
ans = n;
for(int i=2; i*i<=n; ++i){
if(n%i == 0){
int cn = 0;
while(!(n%i)) n /= i, ++cn;
ans /= i; ans *= i*cn - cn + i;
}
}
if(n != 1){ ans /= n; ans *= n + n - 1;}
return cout<<ans, 0;
}
求 \(\sum\limits_{i=2}^{n}\phi(i)\) 的值。进行欧拉筛的同时利用前缀和预处理即可。也可进行离线操作。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 1;
int phi[N], pr[N], cnt, n, sum[N];
bitset<N> vis;
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
for(int i=2; i<=N; ++i){
if(!vis[i]){
pr[cnt++] = i;
phi[i] = i - 1;
}
for(int j=0; j<cnt; ++j){
if(i*pr[j] > N) break;
vis[i*pr[j]] = 1;
if(i%pr[j] == 0){ phi[i*pr[j]] = phi[i] * pr[j]; break;}
phi[i*pr[j]] = phi[i] * phi[pr[j]];
}
sum[i] = sum[i-1] + phi[i];
}
while(cin>>n && n) cout<<sum[n]<<'\n';
return 0;
}
首先,根据欧拉定理可知:
这个定理的好处就在于可以大大降低指数的大小。以下内容用到了这个狮子。
现在来观察题目:若要求 \(a_1^{a_2^{a_3}}mod\ 10007\) ,可以先缩小指数,求出 \(a_2^{a_3}mod\ \phi(10007)\ +\phi(10007)\)。但还是不够,再一次缩小指数,求出 \(a_3mod\ \phi(\phi(10007))\ +\phi(\phi(10007))\)。以此类推,不断缩小指数,最后再使用快速幂取模得出答案。
很显然可以使用递归求解,并且当 \(a\) 的数量超过13的时候,\(\phi\) 值就变成了1,再往上走就没有啥意义了,直接退出。当然,也可以预先求解出所有的 \(\phi\) 值,不多,就13个。
#include<bits/stdc++.h>
using namespace std;
int n, mod[13] = {10007, 10006, 5002, 2400, 640, 256, 128, 64, 32, 16, 8, 4, 2};
inline int getans(int times, int m){
if(!times || m >= 13) return 1;
int a, ans = 1, k;
cin>>a;
k = getans(times-1, m+1);
if(a == 1) return 1; // 注意这里
while(k){
if(k & 1) ans = (long long)ans * a % mod[m];
a = (long long)a * a % mod[m];
k >>= 1;
}
return m?ans + mod[m]:ans;
}
int main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n;
return cout<<getans(n, 0), 0;
}
跟 # 苦恼的小明 一个道理。
首先,根据欧拉定理可知:
所以我们能做的就是不断地利用欧拉定理把指数缩小。比如要求 \(2^{2^2}\ mod\ p\),考虑缩小指数 \(k_1=2^2\ mod\ \phi(p)\),接着缩小 \(k_2=2\ mod\ \phi(\phi(p))\)。如此类推,把得到的新指数用快速幂求解即可。
当然,不难发现,当 \(p!=1\) 时总有 \(\phi(p)<p\)。所以总有一刻 \(\phi=1\) 。那这就好办了,模数都等于1了那再往下递归就没有意义了,所以直接返回1。
#include<bits/stdc++.h>
int p, t;
inline int rd(){
int x = 0; char ch = getchar();
while(!isdigit(ch)) ch = getchar();
while(isdigit(ch)) x = (x<<1) + (x<<3) + (ch^48), ch = getchar();
return x;
}
inline void wt(int k){ if(k/10>0) wt(k/10); putchar(k%10+'0'); }
inline int getans(int mod){
if(mod == 1) return (mod==p)?0:1; //特判p=1的情况
int phi = mod, q = mod;
for(int i=2; i*i<=q; ++i){
if(!(q%i)){
phi /= i, phi *= i - 1;
while(!(q%i)) q /= i;
}
}
if(q != 1) phi /= q, phi *= q - 1; //求出mod的phi值
int k = getans(phi), ans = 1, a = 2; //继续计算下一个指数
while(k){ //快速幂
if(k & 1) ans = (long long)ans * a % mod;
a = (long long)a * a % mod;
k >>= 1;
} return (mod==p)?ans:ans+mod;
}
int main(){
t = rd();
while(t--){
p = rd();
int q = p;
while(!(q%2)) q >>= 1; //特判是否p只有一个等于2的质因子 可要可不要
if(q == 0) putchar('0'), putchar('\n');
else wt(getans(p)), putchar('\n');
}
return 0;
}