题目描述
难度分:983
输入n(1≤n≤2×105)和长为n的数组a(1≤a[i]≤2×105)。
问:有多少个下标三元组(i,j,k)满足a[i]×a[j]=a[k]?
注意i,j,k之间没有大小约束。
输入样例1
3
6 2 3
输出样例1
2
输入样例2
1
2
输出样例2
0
输入样例3
10
1 3 2 4 6 8 2 2 3 7
输出样例3
62
算法
根号枚举+组合数学
由于i,j,k之间没有大小关系,我们可以先对a数组排序,然后遍历k∈[1,n]。由于a[k]≤200000,我们对每个a[k]也可以根号枚举其较小的那个因子d,然后得到另一个因子a[k]d,在遍历的过程中维护一个哈希表counter,用来记录[1,i]中每个元素a[j](j∈[1,i])的频数。对每个a[k](k∈[1,n]),把此时的候选三元组(a[i]=d,a[j]=a[k]d,a[k])放到一个集合triples中。
接下来我们遍历triples,看每个不同的三元组有多少种方案。对于满足x≤y≤z的三元组(x,y,z),分为以下4种情况:
- x=y=z=1,方案数就是counter[x]3。
- x=y<z,可以从counter[x]个x中选两个数充当x和y,方案数为counter[x]2×counter[z]。
- x<y=z,可以从counter[y]个y中选两个数充当y和z,同时x和y是可以交换的,因此方案数为2×counter[x]×counter[y]2。
- x<y<z,x和y可以交换,方案数也是2×counter[x]×counter[y]×counter[z]。
以上所有三元组的方案都累加起来就是答案。
复杂度分析
时间复杂度
遍历k∈[1,n]时间复杂度为O(n),枚举a[k]因子的时间复杂度为O(√N),因此根号枚举的时间复杂度为O(n√N),其中N是a[i]的值域。将候选三元组放入到集合中的时间复杂度可以是O(1),但是在实现的时候由于不想写哈希函数直接用的有序集合,所以真实的时间复杂度会多一个log。
根据值域范围,每个a[k]的因子数不会很大,因此triples中的三元组数目大概就是一个常数比较大的O(n),肯定是小于O(n√N)。所以最后遍历三元组统计答案时,时间复杂度最大就是O(n)。
综上,整个算法的时间复杂度为O(n√N)。
空间复杂度
哈希表counter中存了O(n)级别的键值对,空间复杂度为O(n)。三元组集合triples中存了O(n)级别的三元组,因此整个算法的额外空间复杂度为O(n)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, a[N];
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
if(n < 3) {
puts("0");
return 0;
}
sort(a + 1, a + n + 1);
set<array<int, 3>> triples;
unordered_map<int, long long> counter;
for(int k = 1; k <= n; k++) {
counter[a[k]]++;
for(int d = 1; d <= a[k] / d; d++) {
if(a[k] % d) continue;
if(counter.count(d) && counter.count(a[k]/d)) {
triples.insert({d, a[k]/d, a[k]});
}
}
}
long long ans = 0;
for(auto t: triples) {
int x = t[0], y = t[1], z = t[2];
if(x == z) {
// x=y=z
long long cnt = counter[x];
ans += cnt*cnt*cnt;
}else if(x == y) {
// x=y<z
ans += counter[x]*counter[y]*counter[z];
}else if(y == z) {
// x<y=z
ans += 2*counter[x]*counter[y]*counter[z];
}else {
// x<y<z
ans += 2*counter[x]*counter[y]*counter[z];
}
}
printf("%lld\n", ans);
return 0;
}