【StarryCoding P20】青春异或少女不会遇到lcm学长 题解(数学+辗转相除法)

190 阅读2分钟

题目描述

给定一个数组 aa,求:

i=1nj=in(aiaj)×lcm(ai,aj)\sum_{i=1}^{n}\sum_{j=i}^{n}(a_i \oplus a_j) \times lcm(a_i, a_j)

结果对 109+710^9 + 7 取模。

其中,\oplus 表示异或运算,lcm(a,b)lcm(a,b) 表示 a,ba,b 的最小公倍数。

输入格式

第一行两个整数 nn 表示数组长度。(1n2×105)(1 \leq n \leq 2 \times 10^5)

第二行 nn 个整数,第 ii 个整数表示 aia_i(0ai2×103)(0 \leq a_i \leq 2 \times 10^3)

输出格式

一个数字,表示取模后的结果。

输入样例

3
1 2 3

输出样例

18

提示

样例中 (12)×lcm(1,2)+(13)×lcm(1,3)+(23)×lcm(2,3)=3×2+2×3+1×6=18(1 \oplus 2) \times lcm(1,2) + (1 \oplus 3) \times lcm(1,3) + (2 \oplus 3) \times lcm(2,3) = 3 \times 2 + 2 \times 3 + 1 \times 6 = 18


思路

首先,定义两个辅助函数:gcd 和 lcm,分别用于计算两个数的最大公约数和最小公倍数。

在主函数中,首先使用 memset 函数初始化 cnt 数组,将所有元素设置为0。

接着,从输入中读取数组长度 n,以及 n 个数组元素。对于每个读入的数组元素,增加其在 cnt 数组中的计数。

然后,进行两层循环,分别遍历 i 和 j。当 i 和 j 相等时,它们异或的结果总为0,不用计算这种情况。为了防止重复计算,让 j 始终大于 i。在每一次循环中,计算异或值 (i ^ j),最小公倍数 lcm(i, j),以及 cnt[i] 和 cnt[j] 的乘积。将这三者的乘积累加到 ans 中。

最后,输出累加后的结果 ans。

注意

计算时,每次计算乘法后需要立即对结果取模,防止溢出。


AC代码

#include <cstring>
#include <iostream>
#define AUTHOR "HEX9CF"
using namespace std;
using ll = long long;

const int N = 1e6 + 7;
const ll M = 1e9 + 7;

int n;
ll a[N], cnt[N];

ll gcd(ll x, ll y) { return (y == 0) ? (x) : (gcd(y, x % y)); }

ll lcm(ll x, ll y) { return x / gcd(x, y) * y; }

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);

	memset(cnt, 0, sizeof(cnt));

	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		cnt[a[i]]++;
	}

	ll ans = 0;
	for (ll i = 0; i <= 2e3; i++) {
		for (ll j = i + 1; j <= 2e3; j++) {
			ans = (ans + (i ^ j) * lcm(i, j) % M * cnt[i] % M * cnt[j] % M) % M;
		}
	}
	cout << ans << "\n";
	return 0;
}