题目描述
难度分:2100
输入T(≤2×104)表示T组数据。所有数据的n之和≤106。
每组数据输入n(1≤n≤106)和长为n的数组a(1≤a[i]≤n)。
输出有多少对(i,j),满足i<j且不存在a[k](1≤k≤n)既可以整除a[i]又可以整除a[j]。
输入样例
6
4
2 4 4 4
4
2 3 4 4
9
6 8 9 4 6 8 9 4 9
9
7 7 4 4 9 9 6 2 9
18
10 18 18 15 14 4 5 6 8 9 10 12 15 16 18 17 13 11
21
12 19 19 18 18 12 2 18 19 12 12 3 12 12 12 18 19 16 18 19 12
输出样例
0
3
26
26
124
82
算法
数论容斥
看过数论容斥这题就很简单,不然还是特别难想的。先对数组a求元素的频数,存入到数组cnt中,cnt[i]表示i在a中的出现次数,然后考虑一个问题,fk表示满足gcd(ai,aj)=k的(i,j)对数量,考虑求取fk。
这是个比较经典的做法,nlnn枚举i∈[1,n]所有i在值域[1,n]内的倍数。对于一个i,先将它倍数的频数累加起来得到sum,这sum个数中任意两个配对,都有约数i,一共有C2sum个方案。但是这sum个数挑两个出来有可能约数并不是i,而是2i、3i、…,因此要把这些去掉,则有fi=C2sum−f2i−f3i−…。
最后我们计算答案,枚举i∈[1,n],只要cnt[i]>0,说明i存在于数组a中,以它为约数的f值都不能计入答案,枚举i的倍数k×i,令f[k×i]=0。这样打过标记后,最后的答案就是Σi∈[1,n]fi。
复杂度分析
时间复杂度
计算a中元素的频数,以及最后计算答案遍历Σi∈[1,n]fi,时间复杂度都是O(n)。而计算f的时候,外层遍历i∈[1,n],内层在值域[i,n]内遍历i的倍数,根据调和级数公式n1+n2+…+nn大概就是lnn+C(C为常数),时间复杂度为O(nlnn),这也是整个算法的时间复杂度。
空间复杂度
除去输入的a数组,额外空间主要是cnt和f两个数组。而因为a[i]≤n,这两个数组的空间都是线性的,额外空间复杂度为O(n)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1000010;
int T, n, a[N], cnt[N];
LL f[N];
LL C2(LL n) {
return n*(n - 1)/2;
}
void solve() {
for(int i = 1; i <= n; i++) {
cnt[a[i]]++;
}
for(int i = n; i >= 1; i--) {
LL sum = 0;
for(int j = i; j <= n; j += i) {
sum += cnt[j];
}
f[i] = C2(sum);
for(int j = i + i; j <= n; j += i) {
f[i] -= f[j];
}
}
for(int i = 1; i <= n; i++) {
if(cnt[i] > 0) {
for(int j = i; j <= n; j += i) {
f[j] = 0;
}
}
}
LL ans = 0;
for(int i = 1; i <= n; i++) {
ans += f[i];
}
printf("%lld\n", ans);
}
int main() {
scanf("%d", &T);
while(T--) {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
cnt[i] = f[i] = 0;
}
solve();
}
return 0;
}