题解 | 洛谷P2926 [USACO08DEC]Patting Heads S |数论|约数|超时优化思路|算法时间复杂度分析

题目描述

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);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值