【莫比乌斯反演】C - LCMs - AGC038
题目链接(https://atcoder.jp/contests/agc038/tasks/agc038_c)
Time Limit: 2 sec / Memory Limit: 1024 MB
Score : \(700\) points
Problem Statement
We have an integer sequence of length \(N\): \(A_0,A_1,\cdots,A_{N-1}\).Find the following sum (\(\mathrm{lcm}(a, b)\) denotes the least common multiple of \(a\)and \(b\)):\(\sum_{i=0}^{N-2} \sum_{j=i+1}^{N-1} \mathrm{lcm}(A_i,A_j)\)
Since the answer may be enormous, compute it modulo \(998244353\).
Constraints
\(1 \leq N \leq200000\)
\(1 \leq A_i \leq 1000000\)
All values in input are integers.
Input
Input is given from Standard Input in the following format:
\(N\)
\(A_0\ A_1\ \cdots\ A_{N-1}\)
Output
Print the sum modulo \(998244353\).
Sample Input 1
3 2 4 6
Sample Output 1
22
\(\mathrm{lcm}(2,4)+\mathrm{lcm}(2,6)+\mathrm{lcm}(4,6)=4+6+12=22\).
Sample Input 2
8 1 2 3 4 6 8 12 12
Sample Output 2
313
Sample Input 3
10 356822 296174 484500 710640 518322 888250 259161 609120 592348 713644
Sample Output 3
353891724
题意
给定长度为\(N\)的序列\(A_1,A_2,...,A_N\),求\(\sum_{i = 0}^{N-2}\sum_{j=i+1}^{N-1}lcm(A_i.A_j)\),模\(998244353\)
\((1\leq N\leq 2*10^5,1\leq A_i\leq 10^6)\)
思路
(笔记)
众所周知\(lcm(a,b) == (a * b) / gcd(a,b)\)
设答案为\(ans\),则可以由\(\sum_{i=0}^{N-1}\sum_{j=0}^{N-1}lcm(A_i,A_j) = 2 * ans + \sum_{i=0}^NA_i\)
间接知道\(ans\)大小
\(\sum_{i=0}^{N-1}\sum_{j=0}^{N-1}lcm(A_i,A_j)\)
\(=\sum_{i=0}^{N-1}\sum_{j=0}^{N-1}(A_i*A_j)/gcd(A_i,A_j)\)
\(=\sum_{d=1}^{L}1/d\sum_{i=0}^{N-1}\sum_{j=0}^{N-1}(A_i*A_j)[gcd(A_i,A_j) = d]\)
设\(f(d)=\sum_{i=0}^{N-1}\sum_{j=0}^{N-1}(A_i*A_j)[gcd(A_i,A_j) = d]\)
对\(f(d)\)作倍数和,\(i,j\)相当于是一样的所以后面作平方
\(g(d)=\sum_{d|d'}f(d')=(\sum_{i=0}^{N-1}A_i[d|A_i])^2\)
\(f(d)=\sum_{d|d'}μ(d'/d)g(d')=\sum_{d|d'}μ(d'/d)(\sum_{i=0}^{N-1}A_i[d|A_i])^2\)
最终得到求和式
\(\sum_{i=0}^{N-1}\sum_{j=0}^{N-1}lcm(A_i,A_j)=\sum_{d=1}^L1/d\sum_{d|d'}μ(d'/d)(\sum_{i=0}^{N-1}A_i[d'|A_i])^2\)
在这里,对于任意的\(d\)的这样的一个求和式\(h(d)=\sum_{d|n}f(d)g(n/d)\),可以通过枚举因子或者枚举倍数来求得\(h(n)\),复杂度\(O(nlogn)\)
然后就可以一步步求求和式:
对于求和式,第一项是求所有的\(A_i\)是\(d'\)的倍数的求和, \((\sum_{i=0}^{N-1}A_i[d'|A_i])\),就可以把这里的求和看作\(h(d') = \sum_{i = 0}^{N-1}A_i[d'|A_i]\),把\(A_i\)用一个数组存起来,对这个数组进行倍数和,可以用\(nlogn\)的复杂度求取\(h(d')\),用桶数组\(pos[]\)优化方便累加,即\(h(d') = \sum_{d'|i}pos_i\)
然后就可以来求\(\sum_{d|d'}μ(d'/d)h(d')^2\),又是类似于倍数和的一种形式,可以用\(nlogn\)的复杂度求取\(\sum_{d|d'}μ(d'/d)\),即\(h'(d) = \sum_{d|d'}μ(d'/d)\)这样
最后可以用\(O(n)\)的复杂度求取\(\sum_{d=1}^L1/d*h'(d)\)
总复杂度\(O(LlogL+N),L=maxA_i\)
(嘤嘤嘤,被笨死惹,不想敲注释,名字和思路里的变量都对应的上吧)
AC代码
#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define llinf 0x3f3f3f3f3f3f3f3f
#define int long long
#define ull unsigned long long
#define PII pair<int,int>
#define endl '\n'
const int N = 1e6 + 100;
const int mod = 998244353;
const double pi = acos(-1.0);
typedef long long ll;
using namespace std;
int n;
int mx;
int a[N],pos[N];
int h1[N], h2[N];
int miu[N];
int prime[N], num;
bool vis[N];
int quickpow(int a, int b) {
int res = 1;
while (b > 0) {
if (b & 1) res = res * a % mod;
b >>= 1;
a = a * a % mod;
}
return res;
}
int getinv(int a) {
return quickpow(a, mod - 2);
}
void init() {
mx = 0;
for (int i = 1; i <= n; i++) {
a[i] = 0;
}
return;
}
void solve() {
init();
for (int i = 1; i <= n; i++)
{
cin >> a[i];
pos[a[i]] = (pos[a[i]] + a[i]) % mod;
mx = max(mx, a[i]);
}
miu[1] = 1;
for (int i = 2; i <= 1e6; i++)
{
if (!vis[i]) prime[++num] = i, miu[i] = -1;
for (int j = 1; j <= num && i * prime[j] <= 1e6; j++)
{
vis[i * prime[j]] = 1;
if (i % prime[j] == 0) break;
miu[i * prime[j]] = -miu[i];
}
}
for (int d = 1; d <= 1e6; d++)
{
int d_inv = getinv(d);
for (int dd = d; dd <= 1e6; dd += d)
{
h1[d] = (h1[d] + pos[dd]) % mod;
h2[dd] = (h2[dd] + miu[dd / d] * d_inv % mod + mod) % mod;
}
}
int ans = 0;
for (int d = 1; d <= mx; d++) {
ans = (ans + h1[d] * h1[d] % mod * h2[d] % mod) % mod;
}
ans = ((ans - h1[1]) % mod + mod) % mod;
cout << ans * getinv(2) % mod;
return;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
while (cin >> n) {
solve();
}
return 0;
}

浙公网安备 33010602011771号