题目大意
n n n 个点的完全图,点的编号为 1 ∼ n 1\sim n 1∼n,点 i i i 和 j j j 之间的边权为 lcm ( i + 1 , j + 1 ) \text{lcm}(i+1, j+1) lcm(i+1,j+1) ,求MST的大小。( n ≤ 1 0 10 n\le 10^{10} n≤1010)
思路
将点的编号+1。
点2无贡献,其他点编号为素数的贡献为2,其他点贡献为1。
因此关键要求出
n
n
n 以内的素数和。
需要用到 min_25 筛。
min_25筛通常用来求积性函数 f ( n ) f(n) f(n) 的前缀和,要求是 f ( p ) f(p) f(p) 为关于 p p p 的多项式,且 f ( p c ) f(p^c) f(pc) 可以快速求。
具体做法是先把所有点的取值均当作 f ( p ) f(p) f(p) 的多项式,做一个幂和(参考),然后利用 n \sqrt{n} n 以内的质数,对合数上的值进行修正。可以记 f i ( n ) f_i(n) fi(n) 为筛掉前 i i i 个质数时的前缀和(可能要搞记忆化)。
回到本题,可以仿照上述过程实现筛法。
代码
#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = l; i <= r; ++i)
typedef long long ll;
const int N = 20000007;
using namespace std;
ll mod;
int t;
int prime[N], tot = 0;
ll sp[N], dp[N];
bool iscmp[N];
void init() { // get primes
dp[0] = dp[1] = 0;
rep(i, 2, N - 1) {
dp[i] = dp[i - 1];
if (!iscmp[i]) {
prime[++tot] = i;
dp[i] += i;
}
for (int j = 1; j <= tot && i * prime[j] < N; ++j) {
iscmp[i * prime[j]] = true;
if (i % prime[j] == 0) break;
}
}
rep(i, 1, tot) sp[i] = sp[i - 1] + prime[i];
}
ll n;
ll f(ll u, ll j) { // g(n/u, j)
// printf("%d %d\n", u, j);
ll now = n / u;
if (now < N && now < 1ll * prime[j] * prime[j]) return dp[now];
while (j && now < 1ll * prime[j] * prime[j]) --j; // useless
if (!j) return (now * (now + 1) / 2 - 1) % mod; // not removed
return ((f(u, j - 1) - (f(u * prime[j], j - 1) - sp[j - 1]) * prime[j]) %
mod +
mod) %
mod;
}
int main() {
init();
// printf("%d\n", sp[26]+5151-5);
scanf("%d", &t);
while (t--) {
scanf("%lld%lld", &n, &mod);
++n;
ll inv2 = (mod + 1) / 2;
ll ans = n % mod * ((n + 1) % mod) % mod * inv2 % mod;
ans = (ans + f(1, tot)) % mod;
ans = (ans + mod - 5) % mod;
printf("%lld\n", ans);
}
return 0;
}