洛谷 P2260 [清华集训2012]模积和

本文详细解析了使用数论分块技巧求解特定数学问题的方法,包括求和公式和时间复杂度分析,提供了完整的代码实现。

恶心至极!!!!!!!!

题目链接

思路

∑ i = 1 n ( n m o d    i ) ∑ j = 1 m ( m m o d    j ) [ i ≠ j ] \sum\limits_{i = 1}^{n} (n \mod i)\sum\limits_{j=1}^{m}(m\mod j)[i\neq j] i=1n(nmodi)j=1m(mmodj)[i=j]

假设没有限制情况 i ≠ j i\neq j i=j

∑ i = 1 n ( n m o d    i ) ∑ j = 1 m ( m m o d    j ) \sum\limits_{i = 1}^{n} (n \mod i)\sum\limits_{j=1}^{m}(m\mod j) i=1n(nmodi)j=1m(mmodj)

只看左半部分:

    ∑ i = 1 n ( n % i ) \ \ \ \sum\limits_{i=1}^{n}(n\% i)    i=1n(n%i)

= ∑ i = 1 n ( n − ⌊ n i ⌋ ∗ i ) = \sum\limits_{i=1}^{n}(n - \lfloor \frac{n}{i}\rfloor * i) =i=1n(nini)

= ∑ i = 1 n n − ∑ i = 1 n ⌊ n i ⌋ ∗ i = \sum\limits_{i=1}^{n}n - \sum\limits_{i = 1}^{n}\lfloor\frac{n}{i}\rfloor*i =i=1nni=1nini

显然数论分块,右半部分同理,都可以数论分块做(余数求和那道题的完全一样的做法)

再看 i = j i=j i=j的情况,即

$ \sum\limits_{i=1}^{k=\min(n,m)}(n\mod i)(m\mod i)$

= ∑ i = 1 k ( n − ⌊ n i ⌋ ∗ i ) ( m − ⌊ m i ⌋ ∗ i ) =\sum\limits_{i=1}^{k}(n-\lfloor\frac{n}{i}\rfloor*i)(m-\lfloor\frac{m}{i}\rfloor*i) =i=1k(nini)(mimi)

= ∑ i = 1 k ( n m − ( ⌊ n i ⌋ ∗ m + ⌊ m i ⌋ ∗ n ) ∗ i + ⌊ n i ⌋ ∗ ⌊ m i ⌋ ∗ i 2 ) =\sum\limits_{i=1}^{k}(nm-(\lfloor\frac{n}{i}\rfloor*m+\lfloor\frac{m}{i}\rfloor*n)*i+\lfloor\frac{n}{i}\rfloor*\lfloor\frac{m}{i}\rfloor*i^2) =i=1k(nm(inm+imn)i+inimi2)

用数论分块求出上面两个式子,用总的减去下面这个式子,注意除法要用逆元

p s ps ps

  1. 能模就模,好事多模

  2. 小知识点

    ∑ i = l r i = ( r − l + 1 ) ∗ ( l + r ) / 2 \sum\limits_{i = l}^{r}i=(r -l + 1)*(l + r) / 2 i=lri=(rl+1)(l+r)/2

    ∑ i = 1 n i 2 = n ∗ ( n + 1 ) ∗ ( 2 ∗ n + 1 ) / 6 \sum\limits_{i = 1} ^{n}i^2= n * (n + 1) * (2* n + 1)/ 6 i=1ni2=n(n+1)(2n+1)/6

时间复杂度 O ( n ) O(\sqrt{n}) O(n )

代码

/*
Author:loceaner
*/
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define int long long
using namespace std;

const int A = 5e5 + 11;
const int B = 1e6 + 11;
const int inv6 = 3323403;
const int inv2 = 9970209;
const int mod = 19940417;
const int inf = 0x3f3f3f3f;

inline int read() {
	char c = getchar();
	int x = 0, f = 1;
	for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
	for( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
	return x * f;
}

int n, m, k;

inline int sum(int l, int r) {
	return (r - l + 1) * (l + r) / 2 % mod;
}

inline int sum1(int r) {
	return r * (r + 1) % mod * (2 * r + 1) % mod * inv6 % mod;
}

inline int sum2(int l, int r) {
	return (sum1(r) - sum1(l - 1)) % mod;
}

inline int solve(int n) {
	int ans = n * n;
	for (int l = 1, r; l <= n; l = r + 1) {
		if (n / l == 0) break;
		r = min(n / (n / l), n);
		ans -= (n / l) % mod * sum(l, r) % mod;
		ans %= mod;
	}
	return (ans % mod + mod) % mod;
}

signed main() {
	n = read(), m = read();
	int ans1 = solve(n) * solve(m) % mod, ans2 = 0;
	for (int l = 1, r, now1, now2, now3, now4; l <= min(n, m); l = r + 1) {
		r = min(n / (n / l), m / (m / l));
		now1 = n * m % mod * (r - l + 1) % mod;
		now2 = ((n / l) * (m / l) % mod * sum2(l, r) % mod + mod) % mod;
		now3 = ((n / l) * m % mod + (m / l) * n % mod) * sum(l, r) % mod;
		now4 = (now1 + now2 - now3 + mod) % mod;
		ans2 = (ans2 + now4) % mod;
	}
	cout << ((ans1 - ans2) % mod + mod) % mod;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值