W
e
l
c
o
m
e
: )

[学习笔记] 乘性函数 - 数论

[SDOI2012] Longge 的问题

我们要求 \(\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})\)

\[\begin{split} \sum\limits_{d\mid n}\frac{n}{d}*\phi (d)&=\sum\limits_{d\mid n}\frac{n}{d}*d\prod\limits_{p_i\mid d}\frac{p_i-1}{p_i}\\ &=\sum\limits_{d\mid n}n*\prod\limits_{p_i\mid d}\frac{p_i-1}{p_i}\\ &=n\sum\limits_{d\mid n}\prod\limits_{p_i\mid d}\frac{p_i-1}{p_i}\\ \end{split} \]

观察上式,我们知道,\(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\),所有的贡献加在一起即为:

\[\begin{split} ans&=1+k_1t_1+k_2t_2+k_1k_2t_1t_2+\dots\\ &=(k_1t_1+1)(k_2t_2+1)\dots\\ &=\prod\limits_{p_i\mid n}k_it_i+1\\ &=\prod\limits_{p_i\mid n}\frac{p_i-1}{p_i}\times k_i+1 \end{split} \]

#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;
}

poj2478 Farey Sequence

\(\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^b\equiv a^{b\ mod\ \phi(p)+\phi(p)}\ mod \ p \]

这个定理的好处就在于可以大大降低指数的大小。以下内容用到了这个狮子。

现在来观察题目:若要求 \(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;
}

上帝与集合的正确用法

image

跟 # 苦恼的小明 一个道理。

首先,根据欧拉定理可知:

\[a^b\equiv a^{b\ mod\ \phi(p)+\phi(p)}\ mod \ p \]

所以我们能做的就是不断地利用欧拉定理把指数缩小。比如要求 \(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;
}
posted @ 2024-05-06 22:35  XiaoLe_MC  阅读(11)  评论(0编辑  收藏  举报