51nod1237 最大公约数之和V3 杜教筛

首先肯定是求
∑ d = 1 n d ∑ i = 1 n ∑ j = 1 n [ g c d ( i , j ) = = d ] \sum_{d = 1}^n d \sum_{i = 1}^n \sum_{j = 1}^n [gcd(i, j) == d] d=1ndi=1nj=1n[gcd(i,j)==d]
直接莫反一下
∑ d = 1 n d ∑ i = 1 [ n d ] ∑ j = 1 [ n d ] ∑ T ∣ g c d ( i , j ) μ [ T ] \sum_{d = 1}^n d \sum_{i = 1}^{[\frac{n}{d}]} \sum_{j = 1}^{[\frac{n}{d}]} \sum_{T | gcd(i,j)} \mu[T] d=1ndi=1[dn]j=1[dn]Tgcd(i,j)μ[T]
T T T提到外面,有
∑ d = 1 n ∑ T = 1 [ n d ] d μ [ T ] [ n d T ] 2 \sum_{d = 1}^n \sum_{T = 1}^{[\frac{n}{d}]} d \mu[T] [\frac{n}{dT}] ^ 2 d=1nT=1[dn]dμ[T][dTn]2
我们可以直接对左边数论分块,那就变成求
∑ i = 1 n μ [ i ] [ n i ] 2 \sum_{i = 1}^n \mu[i][\frac{n}{i}] ^ 2 i=1nμ[i][in]2
这东西当然也可以直接数论分块套杜教筛就好啦。
然后我们算算复杂度,分块套分块是 O ( n 3 4 ) O(n^{\frac{3}{4}}) O(n43)的,杜教筛是 O ( n 2 / 3 ) O(n^{2/ 3}) O(n2/3)的。
我们对于第二个分块可以预处理前 O ( n 2 / 3 ) O(n^{2 / 3}) O(n2/3)个,这样总复杂度就是 O ( n 2 / 3 ) O(n^{2 / 3}) O(n2/3)了。
预处理的话考虑差分,设 S ( n ) = ∑ i = 1 n μ [ i ] [ n i ] 2 S(n) = \sum_{i = 1}^n \mu[i][\frac{n}{i}] ^ 2 S(n)=i=1nμ[i][in]2
然后 S ( n ) − S ( n − 1 ) = ∑ d ∣ n μ [ d ] ∗ [ ( n d ) 2 − ( n d − 1 ) 2 ] S(n) - S(n - 1) = \sum_{d | n} \mu[d] * [(\frac{n}{d}) ^ 2 - (\frac{n}{d} - 1) ^ 2] S(n)S(n1)=dnμ[d][(dn)2(dn1)2]
= 2 ∗ φ [ n ] − [ n = = 1 ] = 2 * \varphi[n] - [n == 1] =2φ[n][n==1]
当然我们还可以对于式子继续化简。
化简的话考虑把 d T dT dT换成 T T T,那么有
∑ T = 1 n [ n T ] 2 ∑ d ∣ T d μ [ T d ] \sum_{T = 1}^n [\frac{n}{T}] ^ 2 \sum_{d | T} d \mu[\frac{T}{d}] T=1n[Tn]2dTdμ[dT]
后面的实际上就是 i d ∗ μ = φ id * \mu = \varphi idμ=φ
那么前面的数论分块,后面的杜教筛也行。

不化简考虑预处理的:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <unordered_map>
#include <ctime>

using namespace std;

using ll = long long;

int const mod = 1e9 + 7;
int const M = 1e7;
int const N = M + 5;

int f[N], pri[M / 3], tot, g[N];
bool isp[N];

void SAI() {
	f[1] = g[1] = 1;
	isp[1] = 1;
	for (int i = 2; i <= M; ++i) {
		if (isp[i] == 0) {
			pri[++tot] = i;
			f[i] = -1;
			g[i] = i - 1;
		}
		for (int j = 1; j <= tot && i * pri[j] <= M; ++j) {
			isp[i * pri[j]] = 1;
			if (i % pri[j]) {
				f[i * pri[j]] = -f[i];
				g[i * pri[j]] = g[i] * g[pri[j]];
			} else {
				f[i * pri[j]] = 0;
				g[i * pri[j]] = g[i] * pri[j];
				break;
			}
		}
	}
	for (int i = 1; i <= M; ++i) {
		f[i] = (1ll * f[i - 1] + 1ll * f[i] + mod * 2) % mod;
		g[i] = (1ll * g[i - 1] + 2ll * g[i]) % mod;
	}
	for (int i = 1; i <= M; ++i)
		g[i] = (g[i] + mod - 1) % mod;
}

unordered_map<ll, int> mp;

ll sum_(ll x) {
	if (x <= M)
		return f[x];
	if (mp.count(x))
		return mp[x];
	ll ret = 1;
	for (ll l = 2, r; l <= x; l = r + 1) {
		r = x / (x / l);
		ret += mod - sum_(x / l) * ((r - l + 1) % mod) % mod;
		if (ret >= mod)
			ret -= mod;
	}
	ret = (ret + mod) % mod;
	mp[x] = ret;
	return ret;
}

ll calc(ll n) {
	if (n <= M)
		return g[n];
	ll ret = 0;
	for (ll l = 1, r; l <= n; l = r + 1) {
		r = n / (n / l);
		ll nd = (n / l) % mod;
		ret += nd * nd % mod * (sum_(r) - sum_(l - 1)) % mod;
		if (ret < 0)
			ret += mod;
		if (ret >= mod)
			ret -= mod;
	}
	return (ret + mod) % mod;
}

int main() {
	ll n, ans = 0;
	cin >> n;
	SAI();
	for (ll l = 1, r; l <= n; l = r + 1) {
		r = n / (n / l);
		ll t = calc(n / l);
		ll t1 = l + r, t2 = r - l + 1;
		if (t1 & 1)
			t2 >>= 1;
		else
			t1 >>= 1;
		if (t1 >= mod)
			t1 %= mod;
		if (t2 >= mod)
			t2 %= mod;
		ans += t1 * t2 % mod * t % mod;
		if (ans >= mod)
			ans -= mod;
	}
	cout << ans << '\n';
}

化简后直接分块杜教筛的::

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <unordered_map>

using namespace std;

using ll = long long;

ll const mod = 1e9 + 7;
int const M = 1e7;
int const N = M + 5;

ll KSM(ll a, ll k) {
	ll ret = 1;
	for (; k; k >>= 1, a = a * a % mod)
		if (k & 1)
			ret = ret * a % mod;
	return ret;
}

int f[N], pri[M / 3], tot;
bool isp[N];
ll inv2;

void SAI() {
	f[1] = 1;
	isp[1] = 1;
	for (int i = 2; i <= M; ++i) {
		if (isp[i] == 0) {
			pri[++tot] = i;
			f[i] = i - 1;
		}
		for (int j = 1; j <= tot && 1ll * i * pri[j] <= M; ++j) {
			isp[i * pri[j]] = 1;
			if (i % pri[j]) 
				f[i * pri[j]] = f[i] * f[pri[j]];
			else {
				f[i * pri[j]] = f[i] * pri[j];
				break;
			}
		}
	}
	for (int i = 1; i <= M; ++i) 
		f[i] = (1ll * f[i - 1] + 1ll * f[i] + mod * 2) % mod;
}

unordered_map<ll, ll> mp;

ll sum_(ll x) {
	if (x <= M)
		return f[x];
	if (mp.count(x))
		return mp[x];
	ll ret = ((1 + x) % mod) * (x % mod) % mod * inv2 % mod;
	for (ll l = 2, r; l <= x; l = r + 1) {
		r = x / (x / l);
		ret = ((mod - sum_(x / l) * ((r - l + 1) % mod) % mod) + ret) % mod;
	}
	mp[x] = ret;
	return ret;
}

int main() {
	SAI();
	inv2 = KSM(2, mod - 2);
	ll n, ans = 0;
	cin >> n;
	for (ll l = 1, r; l <= n; l = r + 1) {
		r = n / (n / l);
		ll nd = (n / l) % mod;
		ans += nd * nd % mod * (sum_(r) - sum_(l - 1)) % mod;
		ans %= mod;
	}
	ans = (ans + mod) % mod;
	cout << ans << '\n';
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值