题解 P9091 【「SvR-2」Let's Meet at a Higher Place】
令人遗憾的是本题唯一赛时通过者 @Silver187 写的是经过大力卡常后的算法三。
算法一
容易发现前缀
枚举
算法二
考虑
接下来考虑
考虑钦定其中
注意到
原式
注意到我们可以直接枚举
原式
注意到最后一个求和符号的结果只与
原式
注意到我们可以直接枚举
注意到最后一个求和符号的值只与
原式
线性筛所有
算法三
事实上我们只需要对于
数论分块 + 根号分治或递推版 Min_25 筛求解即可。时间复杂度为
算法四
考虑
于是不难想到 Powerful Number 筛。构造
显然可以算贡献,可得:
但如果此时直接像杜教筛一样根号分治,块筛部分的时间复杂度为
考虑 Powerful Number 的性质。对于
时间复杂度为
算法五
感谢 @渐变色 提供本部分做法。
注意到
于是我们只需要求
时间复杂度为
其实可以出加强版,但是懒得出了(
代码:
#include <stdio.h>
#include <math.h>
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef __int128 lll;
typedef __uint128_t ulll;
typedef struct Division_tag {
ulll a;
Division_tag(){}
Division_tag(ull mod){
a = (((ulll)1 << 64) + mod - 1) / mod;
}
} Division;
const int N = 1e5 + 7, M = 2e5 + 7, K = 67 + 7, P = 33 + 7, Q = 10 + 7;
int sqrt_n, prime_cnt = 0, id = 0;
ll cur_n;
int prime[N], max_prime_cnt[M];
uint c[K][P], g[M], h[Q][M], prime_power[P];
ll number[M];
bool p[N];
Division inv_prime[N];
ull operator /(const ull &a, const Division &b){
return a * b.a >> 64;
}
inline int min(int a, int b){
return a < b ? a : b;
}
inline int log2(ll n){
int ans = log2((double)n);
while ((1ll << ans) <= n) ans++;
return ans - 1;
}
inline int get_max_prime_cnt(ll n, int cnt){
int ans = 0;
for (register ll i = 1; i <= n && ans <= cnt; i *= prime[ans]) ans++;
return ans - 1;
}
inline int divide(ll x, ll y){
return 1.0 * x / y;
}
inline int get_id(ll n){
return n <= sqrt_n ? id - n + 1 : divide(cur_n, n);
}
inline void init(ll n, int m){
int up1 = log2(n), up2 = up1 + m - 1;
sqrt_n = sqrt(n);
cur_n = n;
c[0][0] = 1;
for (register int i = 1; i <= up2; i++){
int t = min(i, up1);
c[i][0] = 1;
for (register int j = 1; j <= t; j++){
c[i][j] = c[i - 1][j] + c[i - 1][j - 1];
}
}
p[0] = p[1] = true;
for (register int i = 2; i <= sqrt_n; i++){
if (!p[i]){
prime_cnt++;
prime[prime_cnt] = i;
inv_prime[prime_cnt] = Division(i);
}
for (register int j = 1; j <= prime_cnt && i * prime[j] <= sqrt_n; j++){
p[i * prime[j]] = true;
if (i % prime[j] == 0) break;
}
}
for (register ll i = 1, j; i <= n; i = j + 1){
ll tn = n / i;
j = n / tn;
id++;
number[id] = tn;
max_prime_cnt[id] = get_max_prime_cnt(tn, prime_cnt);
g[id] = tn - 1;
}
for (register int i = 1; i <= prime_cnt; i++){
ll t = 1ll * prime[i] * prime[i];
for (register int j = 1; j <= id && number[j] >= t; j++){
g[j] -= g[get_id(number[j] / inv_prime[i])] - (i - 1);
}
}
for (register int i = 1; i <= id; i++){
h[0][i] = 1;
h[1][i] = g[i];
}
for (register int i = prime_cnt; i >= 1; i--){
ll t1 = 1ll * prime[i] * prime[i];
for (register int j = 1; j <= id && number[j] >= t1; j++){
int cur_id = get_id(number[j] / inv_prime[i]), t2 = max_prime_cnt[j];
h[2][j] -= i;
for (register int k = 2; k <= t2; k++){
h[k][j] += h[k - 1][cur_id];
}
}
}
}
inline uint get_f__sum(ll n, int m){
int id = get_id(n);
uint ans = 0;
for (register int i = max_prime_cnt[id]; i >= 0; i--){
ans = ans * m + h[i][id];
}
return ans;
}
uint get_f_sum(int cur, int m, ll val1, uint val2){
uint ans = val2 * get_f__sum(cur_n / val1, m);
for (register int i = cur + 1; i <= prime_cnt && (lll)val1 * prime[i] * prime[i] <= cur_n; i++){
int j = 2;
for (register ll x = val1 * prime[i] * prime[i]; x <= cur_n; j++, x *= prime[i]){
ans += get_f_sum(i, m, x, val2 * prime_power[j]);
}
}
return ans;
}
int main(){
int m, mi;
ll n;
scanf("%lld %d", &n, &m);
mi = m + 1;
init(n, mi);
prime_power[0] = 1;
for (register int i = 1; (1ll << i) <= n; i++){
prime_power[i] = c[i + m][i] - mi * prime_power[i - 1];
}
printf("%u", m * get_f_sum(0, mi, 1, 1));
return 0;
}