题目链接
思路
如果直接用暴力解法,逐个判断其他的数是不是它的约数,这样时间复杂度是$O(n^2)$,数据规模是$10^5$,会超时
假设$A_1$是$A_2$的约数的话,那么$A_2$就是$A_1$的倍数,因此可以做这么一个处理,当判定某个数$A_k$时,我们可以把这个数字的倍数$A_m$全部加1
,含义是,$A_m$的约数个数又多了1
。
但又考虑到可能会有很多个$A_k$的值是相同的,所以类似于计数排序的思想,先统计$A_k$出现的次数,然后再做上面的操作会方便一些
统计a[i]出现的次数:
for(int i = 0; i < n; i++) {
scanf("%d", &a[i]);
cnt[a[i]]++;
}
$cnt[i] != 0$,即有某个$A_k$出现了,把$A_k$的倍数j
加上$A_k$出现的次数,即j
的约数个数又多了$cnt[A_k]$个
for(int i = 1; i < N; i++) {
if(!cnt[i]) continue;
//i其实就是Ak,j就是i的倍数
for(int j = i; j < N; j += i) {
res[j] += cnt[i];
}
}
最后,要在所有数中求$A_k$约数个数,$res[A_k]$就是$A_k$约数的个数(包括$A_k$自己)。可以想象,假设$A_k = 4$,它在所有数中有两个约数$A_1 = A_2 = 2$,经过上述操作后,$res[4]$的值应该是3,所以$A_k$的约数个数是$res[A_k] - 1 = 3 - 1 = 2$
总结
约数和倍数其实是一对好基友,当求约数的时间复杂度过大时,不妨从它倍数的角度来考虑解决问题
代码
#include<iostream>
using namespace std;
const int N = 1e6 + 10, M = 1e5 + 10;
int cnt[N], a[M], res[N];
int n;
int main() {
scanf("%d", &n);
for(int i = 0; i < n; i++) {
scanf("%d", &a[i]);
cnt[a[i]]++;
}
for(int i = 1; i < N; i++) {
if(!cnt[i]) continue;
for(int j = i; j < N; j += i) {
res[j] += cnt[i];
}
}
for(int i = 0; i < n; i++) printf("%d\n", res[a[i]] - 1);
return 0;
}
写的很清楚
为啥cnt数组改成 map or unordered_map就TLE啊 有没有大佬教教~
假设你写的没问题,那应该是以下原因导致:
1.map一次插入O(logn)而非O(1)
2.unordered_map一次插入容易被卡成O(n)
3.STL常数大,被卡常了
我的思路 TLE了 复杂度是nlg(max(a[i])) 最大约10^8
枚举每个a[i] 算其约数出现的次数,题主的方法很巧妙!
题主的方法更像打表
感觉复杂度也不低
其实这样的代码循环时间复杂度是 $O(n \log n)$
他这个for循环的第二层运行的次数是一个调和级数n + n/2 + n/3 + n/4 +.....1 可以证明时间复杂度是lnN级别的
%%%%%%%
%%%%%%%
好基友好评。