【luogu3768】简单的数学题 欧拉函数(欧拉反演)+杜教筛

题目描述

给出 np ,求 (i=1nj=1nijgcd(i,j))modp

n1010


题解

欧拉函数(欧拉反演)+杜教筛

推式子:

(1)i=1nj=1nijgcd(i,j)(2)=i=1nj=1nijd|gcd(i,j)φ(d)(3)=i=1nj=1nijd|i,d|jφ(d)(4)=d=1nφ(d)i=1ndidj=1ndjd(5)=d=1nd2φ(d)(i=1ndi)2(6)=d=1nd2φ(d)(nd(nd+1)2)2

nd 分块处理,只需要求出 f(n)=n2φ(n) 的前缀和即可。

显然这是一个积性函数,然而 n1010 之大,不能线性筛。

考虑杜教筛。设 g(n)=n2 ,那么有:

(7)(f·g)(n)(8)=d|nf(d)g(nd)(9)=d|nd2φ(d)·(nd)2(10)=n2d|nφ(d)(11)=n3

所以:

(12)i=1ni3(13)=i=1n(f·g)(i)(14)=i=1nd|if(d)·g(id)(15)=i=1nd|if(id)g(d)(16)=d=1ng(d)i=1ndf(i)(17)=d=1nd2S(nd)

故有:

S(n)=i=1ni3i=2ni2S(nd)

线性筛预处理出 n23 以内的 S(i) ,对超过 n23 的部分进行杜教筛即可。

可能需要用到的公式:

i=1ni2=n(n+1)(2n+1)6i=1ni3=n2(n+1)24

时间复杂度 O(n23) ,这里偷懒使用map,复杂度多一个 log

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <map>
#include <cstdio>
#define N 10000010
using namespace std;
typedef long long ll;
const int m = 10000000;
map<ll , ll> mp;
int phi[N] , prime[N] , tot , np[N];
ll sum[N] , p , inv4 , inv6;
void init()
{
    int i , j;
    phi[1] = 1;
    for(i = 2 ; i <= m ; i ++ )
    {
        if(!np[i]) phi[i] = i - 1 , prime[++tot] = i;
        for(j = 1 ; j <= tot && i * prime[j] <= m ; j ++ )
        {
            np[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] * phi[prime[j]];
        }
    }
    for(i = 1 ; i <= m ; i ++ ) sum[i] = (sum[i - 1] + 1ll * phi[i] * i % p * i) % p;
    inv4 = (p + 1) / 2;
    if(p % 3 == 1) inv6 = (2 * p + 1) / 3;
    else inv6 = (p + 1) / 3;
    inv6 = inv6 * inv4 % p , inv4 = inv4 * inv4 % p;
}
ll calc2(ll n) {n %= p; return n * (n + 1) % p * (2 * n + 1) % p * inv6 % p;}
ll calc3(ll n) {n %= p; return n * n % p * (n + 1) % p * (n + 1) % p * inv4 % p;}
ll solve(ll n)
{
    if(n <= m) return sum[n];
    if(mp.find(n) != mp.end()) return mp[n];
    ll i , last , ans = calc3(n);
    for(i = 2 ; i <= n ; i = last + 1) last = n / (n / i) , ans = (ans - (calc2(last) - calc2(i - 1) + p) * solve(n / i) % p + p) % p;
    return mp[n] = ans;
}
int main()
{
    ll n , i , last , ans = 0;
    scanf("%lld%lld" , &p , &n);
    init();
    for(i = 1 ; i <= n ; i = last + 1)
        last = n / (n / i) , ans = (ans + (solve(last) - solve(i - 1) + p) * calc3(n / i)) % p;
    printf("%lld\n" , ans);
    return 0;
}

 

posted @   GXZlegend  阅读(675)  评论(0编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
历史上的今天:
2017-04-05 【bzoj1263】[SCOI2006]整数划分 高精度
2017-04-05 【bzoj4551】[Tjoi2016&Heoi2016]树 并查集
2017-04-05 【bzoj4517】[Sdoi2016]排列计数 组合数+dp
点击右上角即可分享
微信分享提示