积性函数和完全积性函数在欧拉筛里的情况
对于函数(f(x))
然后式子(f(x y) = f(x) * f(y))
如果说只有x和y互质的时候才成立,那么(f(x))是一个积性函数
如果说对于任意的x和y都成立,那么(f(x))是一个完全积性函数
在线性筛里面,如果说(f(x))是一个积性函数,那么
void pri_table(int N){
vis[1] = vis[0] = 1;
F[1] = cal(1);
for(int i = 2; i <= N; i++) {
if(!vis[i]) {
pri[++tot] = i;
F[i] = cal(i);
}
for(int j = 1; j <= tot; j++) {
if(i * pri[j] > N) break;
vis[i * pri[j]] = 1;
if(i % pri[j] == 0) {
//考虑维护质数时的值更新即可
break;
}else F[i * pri[j]] = F[i] * F[pri[j]];//积性函数情况
}
}
}
如果是一个完全积性函数,那么可以直接筛
void pri_table(int N){
vis[1] = vis[0] = 1;
F[1] = cal(1);
for(int i = 2; i <= N; i++) {
if(!vis[i]) {
pri[++tot] = i;
F[i] = cal(i);
}
for(int j = 1; j <= tot; j++) {
if(i * pri[j] > N) break;
vis[i * pri[j]] = 1;
F[i * pri[j]] = F[i] * F[pri[j]];
if(i % pri[j] == 0) break;//维护质数时的值更新
}
}
}
题目
比如题目传送门
其中(f(x) = x ^ n)是一个完全积性函数
(f(xy) = (xy) ^ n = x^n y^n)
那么就利用(F[i * pri[j]] = F[i] * F[pri[j]])即可
对于质数就直接利用快速幂求
素数个数(frac{1.5n}{logn})左右,每个素数的快速幂是(O(logn))那么时间复杂度就是(O(n))
#include <iostream>
#include <cstdio>
#include <cmath>
#define ll long long
using namespace std;
const int N = 1e7 + 5;
const int mod = 1e9 + 7;
int pri[N], tot;
bool vis[N];
ll F[N];
ll pow(ll a, ll b, ll p){
ll ans = 1; a %= p;
while(b){
if(b & 1) ans = ans * a % p;
a = a * a % p;
b >>= 1;
}
return ans;
}
void pri_table(int N, int n){
vis[1] = vis[0] = 1;
F[1] = 1;
for(int i = 2; i <= N; i++) {
if(!vis[i]) {
pri[++tot] = i;
F[i] = pow(i, n, mod);
}
for(int j = 1; j <= tot; j++) {
if(i * pri[j] > N) break;
vis[i * pri[j]] = 1;
F[i * pri[j]] = (ll)F[i] * F[pri[j]] % mod;
if(i % pri[j] == 0) break;
}
}
}
int main(){
int n;
scanf("%d", &n);
pri_table(N - 3, n);
ll ans = 0;
for(int i = 1; i <= n; i++)
ans = ans ^ F[i];
printf("%lld
", ans);
return 0;
}
对于题目传送门
也是一个线性筛问题,其实(f(n,kk))表示把一个数(n)划分为(kk)个数字乘积的个数
根据唯一分解定理(n = p_1^{a_1} p_2^{a_2}...p_k^{a_k} = prod_{i = 1}^kp_i^{a_i})
那么就是说(f(n,kk) = prod_{i = 1}^k g(a_i, kk))
而(g(a_i, kk))表示把(a_i)划分为(kk)个数字相加的情况数。
因为(p_i)都是质数,那么也就是每一个质数都是独立的,因为几个质数的乘积不能用另外几个质数的乘积来表示
比如(a_1)的一组方案(p_1^1 imes p_1^2 imes p_1 ^ 3),(a_2)的一组方案(p2^2 imes p_2^1 imes p_3^0)
那么他们组合就是((p_1^1 p_2^3) imes (p_1^2 p_2^1) imes (p_1^3 p_2^0)), 总共的方案数就是两个的方案数的乘积
转换为模型(sum_{i = 1}^kx_i = t)的非负整数解的个数
如果是求一个数,那么直接对数字质因子分解,然后求组合数,再乘起来就行了
但是对于n个数字,就需要采用线性筛
把x拆分为(x = y imes p^q)
其中(gcd(y, p) = 1),且p是素数
那么(f(x, k) = f(y, k) imes f(p^q, k) = f(y, k) imes left(egin{array}{c}
q+k-1 \
k-1
end{array}
ight))
其中(f(p, k) = k),(p)是质数
那么对于质数倍数时的更新从(C_{num + k - 1} ^ {k - 1}) 变成了 (C_{num + k} ^ {k - 1})
化简后得到值扩大了(frac{num + k}{num + 1})倍,其实num表示数字含有的质数个数
那么直接线性筛就行了
#include <iostream>
#include <cmath>
#include <cstdio>
#define ll long long
using namespace std;
const int mod = 1e9 + 7;
const int N = 1e7 + 5;
int pri[N], num[N], tot;
ll F[N], inv[N];
bool vis[N];
void pri_table(int N, ll k){
F[1] = 1, inv[1] = 1;
for(int i = 2; i <= N; i++)
inv[i] = (ll)(mod - mod / i) * inv[mod % i] % mod;
for(int i = 2; i <= N; i++) {
if(!vis[i]) {
pri[++tot] = i;
num[i] = 1;
F[i] = k;
}
for(int j = 1; j <= tot; j++) {
if(i * pri[j] > N) break;
vis[i * pri[j]] = 1;
if(i % pri[j] == 0) {
num[i * pri[j]] = num[i] + 1;
F[i * pri[j]] = F[i] * (num[i] + k) % mod * inv[num[i] + 1] % mod;
break;
}else {
num[i * pri[j]] = 1;
F[i * pri[j]] = F[i] * k % mod;
}
}
}
}
int main(){
int n; ll k; scanf("%d%lld", &n, &k);
k %= mod;
pri_table(N - 3, k);
ll ans = 0;
for(int i = 1; i <= n; i++)
ans ^= (F[i] + i) % mod;
printf("%lld
", ans);
return 0;
}
其中逆元的个数可以优化,减少空间,因为逆元最大是(num[i] + 1),也就是(2^n),n的值也就在32以内
总结
- 注意函数是积性函数还是完全积性函数,因为两种函数在欧拉筛里放的位置不同
- 注意当(i)是(pri[i])的倍数时,所维护值的一个更新情况