【题解】Luogu-P1447 [NOI2010] 能量采集

Description

  • 给定整数 n , m n, m n,m,求

∑ i = 1 n ∑ j = 1 m 2 gcd ⁡ ( i , j ) − 1 \sum_{i = 1}^n \sum_{j = 1}^m 2 \gcd(i, j) - 1 i=1nj=1m2gcd(i,j)1

  • 对于 100 % 100\% 100% 的数据: 1 ≤ n , m ≤ 1 0 5 1 \le n, m \le 10^5 1n,m105

Solution

不妨设 n ≤ m n\le m nm
∑ i = 1 n ∑ j = 1 m 2 gcd ⁡ ( i , j ) − 1 = − n m + 2 ∑ i = 1 n ∑ j = 1 m gcd ⁡ ( i , j ) \begin{aligned} \sum_{i = 1}^n \sum_{j = 1}^m 2 \gcd(i, j) - 1 & = - nm + 2 \sum_{i = 1}^n \sum_{j = 1}^m \gcd(i, j) \end{aligned} i=1nj=1m2gcd(i,j)1=nm+2i=1nj=1mgcd(i,j)

把后面一项单独拎出来,就是大家喜闻乐见的欧拉反演。
∑ i = 1 n ∑ j = 1 m gcd ⁡ ( i , j ) = ∑ i = 1 n ∑ j = 1 m ∑ d ∣ gcd ⁡ ( i , j ) φ ( d ) = ∑ d = 1 n φ ( d ) ∑ i = 1 n [ d ∣ i ] ∑ j = 1 m [ d ∣ j ] = ∑ d = 1 n φ ( d ) ⌊ n d ⌋ ⌊ m d ⌋ \begin{aligned} \sum_{i = 1}^n \sum_{j = 1}^m \gcd(i, j) & = \sum_{i = 1}^n \sum_{j = 1}^m \sum_{d \mid \gcd(i, j)} \varphi(d) \\ & = \sum_{d = 1}^n \varphi(d) \sum_{i = 1}^n [d\mid i] \sum_{j = 1}^m [d\mid j] \\ & = \sum_{d = 1}^n \varphi(d) \left\lfloor\dfrac{n}{d}\right\rfloor \left\lfloor\dfrac{m}{d}\right\rfloor \end{aligned} i=1nj=1mgcd(i,j)=i=1nj=1mdgcd(i,j)φ(d)=d=1nφ(d)i=1n[di]j=1m[dj]=d=1nφ(d)dndm
预处理前缀和 + 整除分块即可。

14:21 我试试能不能用杜教筛来卡最优解

14:31 我杜教筛 TLE 了

14:43 我杜教筛用了 37 m s 37ms 37ms???用容斥才 25 m s 25ms 25ms

14:48 哦是因为数据范围太小然后杜教筛常数太大

Code

// 18 = 9 + 9 = 18.
#include <iostream>
#include <cstdio>
#include <unordered_map>
#define Debug(x) cout << #x << "=" << x << endl
typedef long long ll;
using namespace std;

const int MAXN = 2154 + 5;
const int N = 2154;

int p[MAXN], phi[MAXN];
ll sum[MAXN];
bool vis[MAXN];

void pre()
{
	phi[1] = sum[1] = 1;
	for (int i = 2; i <= N; i++)
	{
		if (!vis[i])
		{
			p[++p[0]] = i;
			phi[i] = i - 1;
		}
		for (int j = 1; j <= p[0] && i * p[j] <= N; j++)
		{
			vis[i * p[j]] = true;
			if (i % p[j] == 0)
			{
				phi[i * p[j]] = phi[i] * p[j];
				break;
			}
			phi[i * p[j]] = phi[i] * phi[p[j]];
		}
		sum[i] = sum[i - 1] + phi[i];
	}
}

unordered_map<ll, ll> dp;

ll sublinear(int n)
{
	if (n <= N)
	{
		return sum[n];
	}
	if (dp.find(n) != dp.end())
	{
		return dp[n];
	}
	ll res = (ll)n * (n + 1) / 2;
	for (int l = 2, r; l <= n; l = r + 1)
	{
		int k = n / l;
		r = n / k;
		res -= (r - l + 1) * sublinear(k);
	}
	return dp[n] = res;
}

ll getsum(int l, int r)
{
	return sublinear(r) - sublinear(l - 1);
}

ll block(int n, int m)
{
	ll res = 0;
	for (int l = 1, r; l <= n; l = r + 1)
	{
		int k1 = n / l, k2 = m / l;
		r = min(n / k1, m / k2);
		res += getsum(l, r) * k1 * k2;
	}
	return res;
}

int main()
{
	pre();
	int n, m;
	scanf("%d%d", &n, &m);
	if (n > m)
	{
		swap(n, m);
	}
	printf("%lld\n", - (ll)n * m + 2 * block(n, m));
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值