题目描述
P2926 [USACO08DEC]Patting Heads S - 洛谷
题目简化描述:
给定n个正整数,求每个数是另外多少个数的倍数(不包括自己)
思路与分析
- 暴力枚举 n 2 n^2 n2 ×
- 枚举约数 n A n\sqrt{A} nA ×
- 枚举倍数
n
l
o
g
n
nlogn
nlogn (调和级数复杂度)
- 每个数出现i次,则对这个数的每一个倍数(包括自己)都产生i的贡献
我的题解
- 评测分数78,还是有三个数据点超时的解法
#include <iostream>
#include<vector>
using namespace std;
int main() {
// 题目所给的数据范围是[1,1e6]
vector<int>count(1000001, 0);
int N;cin >> N;
vector<int>nums(N);
for (int i = 0; i < N; i++) {
cin >> nums[i];
for (int j = nums[i]; j < 1000001; j += nums[i]) {
count[j]++;
}
}
for (int i = 0; i < N; i++) {
// 上面的逻辑严格按照倍数进行累计贡献(包括数自己)
// 但是按照题目要求 需要减去自己(不包括自己)
cout << count[nums[i]]-1 << endl;
}
}
优化思路如下:
- 算法角度优化:因为每一个数有重复的,所以在找倍数的时候可以加上一个小的优化,不要每次加一,就一次性加上重复出现的次数就好了。为了更好地实现,我们可以设一个桶,输入时处理
- 为什么这样修改可以实现优化?
- 边界情况:考虑当数据量N取最大
- 复杂度差异点:程序中的那个双层for循环,即实现约数贡献传递的部分
- 改进之前:外层循环1e5,内层循环取决于所取的数值1e6/nums[i]
- 改进之后:外层1e6/重复个数,内层1e6/i,即调和级数复杂度
- 不要天然地认为外循环for元素个数会肯定优于循环所有取值,实际上两者的复杂度上限差不多;进一步在存在重复值可的情况下,后者采用合并处理的方式反而会节约时间;再考虑一个极限测试样例,当取值中大量重复的1且取值最大接近1e6的时候,前者就退化到了O(NM)!
- 为什么这样修改可以实现优化?
- 编程角度优化:
- C语言风格输入输出比cin/cout要快很多
- 慎用vector,使用数组
#include<bits/stdc++.h>
using namespace std;
const int Nmax = 1e6 + 2;
int counts[Nmax]; //记录每个数出现的约数个数
int cnt[Nmax]; //每个数的输入个数 索引的数值本身而不是输入的编号!!
int nums[Nmax/10];
int main() {
int N; cin >> N;
int maxNum = 0;
for (int i = 0; i < N; i++) {
// cin >> nums[i];
scanf("%d", &nums[i]);
cnt[nums[i]]++;
maxNum = max(nums[i], maxNum); //记录最大数值,减小后序遍历范围
}
for (int i = 1; i <= maxNum; i++) {
if (cnt[i]) {
for (int j = i; j <= maxNum; j += i) {
counts[j] += cnt[i];
}
}
}
for (int i = 0; i < N; i++) {
// cout << counts[nums[i]] - 1 << endl;
printf("%d\n", counts[nums[i]] - 1);
}
}