题目传送门
题意:
计算 。
是素数。
数据范围: 。
题解:
这道题主要是考杜教筛。
这道题是 这篇博客 的强化版,就接着说了。
设 。
可以整除分块套整除分块求解,现在的复杂度是 ,还需要优化,考虑常用套路换元。
令 。
然后有一个常用的连接欧拉函数和莫比乌斯函数的式子: 。
设 。
然后考虑整除分块去做, 是可以 计算的,主要就是计算 的前缀和。
因为前缀和必须低于线性计算,所以我们考虑杜教筛。
杜教筛常用套路:为了求 ,我们构造积性函数 ,然后令 , 是狄利克雷卷积。
然后如果 很容易计算的话,那我们就可以求出来 了。
设 。
杜教筛公式:
然后我们怎么构造积性函数 呢?
先把 写出来吧。
然后其实是一个套路,我们考虑消除 的方法去构造 。
令 。
然后又是一个常用式子: 。
然后 。
转化为
然后我们就可以递归求解 了。
有两个需要优化的地方:
(1)你需要线性筛 以内的 ,这样会少递归很多次。
(2) 存储递归求解出的 ,这样也会少递归很多次。
感受:
第一次做杜教筛,感觉有点像高中学的化学方程式配平。
代码:
#include<bits/stdc++.h>
using namespace std ;
typedef long long ll ;
typedef pair<int , int> pli ;
const int maxn = 5e6 + 5 ;
ll mod ;
ll six , two ;
map<ll , ll> s ;
int phi[maxn] ;
ll pre[maxn] ;
int num = 5e6 ;
void eular(int n)
{
phi[1] = 1 ;
for(int i = 2 ; i <= n ; i ++) phi[i] = i ;
for(int i = 2 ; i <= n ; i ++)
if(phi[i] == i)
for(int j = i ; j <= n ; j += i)
phi[j] = phi[j] / i * (i - 1) ;
for(int i = 1 ; i <= n ; i ++)
pre[i] = pre[i - 1] + ll(i) * i % mod * phi[i] % mod ,
pre[i] %= mod ;
}
ll qpow(ll a , ll b)
{
ll ans = 1 ;
a %= mod ;
while(b)
{
if(b & 1) ans = (ans * a) % mod ;
b >>= 1 , a = (a * a) % mod ;
}
return ans % mod ;
}
ll inv(ll n)
{
return qpow(n , mod - 2) % mod ;
}
ll sum(ll n)
{
n %= mod ;
ll c = n * (n + 1) % mod ;
c *= two , c %= mod ;
return c * c % mod ;
}
ll sum2(ll n)
{
n %= mod ;
ll c = n * (n + 1) % mod * (2 * n + 1) % mod ;
c *= six , c %= mod ;
return c ;
}
ll dls(ll n)
{
if(n <= num) return pre[n] ;
if(s[n]) return s[n] ;
//cout << n << '\n' ;
ll ans = sum(n) ;
for(ll l = 2 , r ; l <= n ; l = r + 1)
{
r = n / (n / l) ;
ll c = (sum2(r) - sum2(l - 1) + mod) % mod ;
ans -= c * dls(n / l) ;
ans %= mod ;
}
ans = (ans + mod) % mod ;
s[n] = ans ;
return ans ;
}
void solve(ll n)
{
ll ans = 0 ;
for(ll l = 1 , r ; l <= n ; l = r + 1)
{
r = n / (n / l) ;
ll c = (dls(r) - dls(l - 1) + mod) % mod ;
ans += c * sum(n / l) ;
ans %= mod ;
}
printf("%lld\n" , ans) ;
}
int main()
{
ll n , m ;
scanf("%lld%lld" , &mod , &n) ;
eular(num) ;
six = inv(6ll) ;
two = inv(2ll) ;
solve(n) ;
return 0 ;
}