最多约数问题

问题描述: 
正整数x的约数是能整除x的正整数。正整数x 的约数个数记为div(x)。例如,1,2,5,10 都是正整数10 的约数,且div(10)=4。设a 和b 是2 个正整数,a≤b,找出a和b之间约数个数最多的数x。 
编程任务: 
对于给定的2个正整数a≤b,编程计算a 和 b 之间约数个数最多的数。 
数据输入: 
输入数据由文件名为input.txt的文本文件提供。文件的第1 行有2 个正整数 a和 b。 
结果输出: 
程序运行结束时,找到a 和b之间约数个数最多的那个数及最多约数个数。

测试数据:【只给出最多约数个数, time limit: 1s】
[1, 36]                               9
[1000000, 2000000]           288
[999998999, 999999999]    1024
[1, 1000000000]                 1344
[999999999, 1000000000]   56
[100, 1000000000]              1344

———————————————————————————————————————————————————

法一:主要就是查找一个整数的约数个数的效率问题,首先想到的是 1-遍历。逐个判断。但是效率太低。

#include <iostream>
#include <fstream>
#include <ctime>
#include <cmath>
using namespace std;

ifstream fin("input.txt");
ofstream fout("output.txt");

clock_t start,finish;
double  total_time;

int div(int n)
{
	int num = 0;
	for (int i = 1; i < sqrt((float)n); i++)
	{
		if (n%i == 0)
		{
			num += 2;
		}
		
	}
	
	if (n == (int)sqrt((float)n)*(int)sqrt((float)n))
	{
		num ++;
	}
	return num;
}

int caculateMaxdiv(int a, int b)
{
	int maxNum = 0;
	for (int i = a;i <= b;i++ )
	{
		if ( maxNum < div(i))
		{
			maxNum = div(i);
		}
	}
	return maxNum;
}

int main()
{
	start = clock();
	int a,b;
	fin>>a>>b;
	fout<<caculateMaxdiv(a,b)<<endl;
	finish = clock();

	total_time = (double)(finish - start)/CLOCKS_PER_SEC;
	fout<<total_time<<" s"<<endl;
	return 0;
}

法二:

思想:设正整数x的质因子分解为
x=p1^N1 × p2^N2 ×……pi^Ni
则 div(x)=(N1+1)(N2+1)……(Ni+1)

#include<iostream>
using namespace std;
#define max Max
const long MAXP = 100000;
long prim[MAXP];
long max, numb, PCOUNT; //max存放最多约数个数,numb存放约数个数最多的数
void primes();          //用筛选法产生质数存于prim数组中
void search(long from, long tot, long num, long low, long up);
int main()
{
	primes();
	long l, u;
	cin >> l >> u;
	if ((l == 1) && (u == 1))
	{
		max = 1;
		numb = 1;
	}
	else
	{
		max = 2;
		numb = l;
		search(1, 1, 1, l, u);
	}
	cout << max << endl << numb << endl;
	system("pause");
	return 0;
}

void primes() 
{
	bool get[MAXP+1];
	long i;
	for (i = 2; i <= MAXP; i++)
		get[i] = true;
	for (i = 2; i <= MAXP; i++)
		if (get[i])
		{
			long j = i + i;
			while (j <= MAXP) 
			{
				get[j] = false;
				j += i;
			}
		}
		long ii, j;
		for (ii = 2, j = 0; ii <= MAXP; ii++)
			if (get[ii]) prim[++j] = ii;
		PCOUNT = j;
}

void search(long from, long tot, long num, long low, long up)
{
	if (num >= 1)
		if ( (tot > max) || ((tot == max) && (num < numb)) )
		{
			max = tot;
			numb = num;
		}
		if ((low == up) && (low > num)) search(from, tot*2, num*low, 1, 1);
		for (long i = from; i <=PCOUNT; i++)
		{
			if (prim[i] > up) return; 
			else
			{
				long j = prim[i], x = low - 1, y = up, n = num, t = tot, m = 1;
				while (true)
				{
					m++;
					t += tot;
					x /= j;
					y /= j;
					if (x == y) break;
					n *= j;
					search(i+1, t, n, x+1, y);
				}
				m = 1 << m;
				if (tot < max / m) return;
			}
		}
}
针对此方法的解析如下(源自网络):

本题的要求是,求出一个给定区间内的含约数最多的整数。
    首先明确一下如何求一个数的约数个数:若一个数N满足:N = A1 N1 * A2 N2 * A3 N3 * …… * Am Nm,则n的约数个数为(N1 + 1) (N2 + 1) (N3 + 1) …… (Nm + 1)。这是可以用乘法原理证明的。
    最浅显的算法是,枚举区间内的每个整数,统计它们的约数个数。这个算法很容易实现,但是时间复杂度却相当高。因为题目约定区间的最大宽度可以达到10^9的数量级,相当庞大。因此,在极限规模时,时间是无法忍受的。所以,我们需要尽量的优化时间。
    分析一下枚举的过程就会发现,如果我们枚举到两个数n和m*n(m为一相对于n较大的质数),那么我们将重复计算n的约数两次。据此,我们发现了枚举效率低的根本所在。为了解决这一重复,我们可以选取另一种搜索方法——以质因子为对象进行深度搜索。
    初始时,令number := 1,然后从最小的质数2开始枚举,枚举包含一个2、两个2……n个2的情况……直至number * 2 n大于区间的上限(max)。对于每种“2^k的情况”,令number := number * 2 n,再枚举3的情况,然后,枚举5的情况、7的情况……方法相同。整个过程是一个深度搜索的过程。当number大于等于区间下限(min)时,我们就找到了一个区间内的数,根据前面介绍的方法,可以得到它的约数个数。所有的区间内的数的约数个数的最大值就是我们要求的目标。
     为什么这种深度搜索可以减少常规枚举过程中的重复问题呢?请看下面的一个例子
     设给定的区间为[6,30],6,18,30为区间内的数,按照常规枚举方法,计算18,30,的时候分别计算了因子6的约数个数,重复计算2次。如果使用上述所说的深度搜索方法,求这3个数的因数个数的路径有一条公共部分,2*3,这一部分只计算了一次,求18只需再乘个3,求30只需再乘个5,相对于常规枚举减少了两次计算2*3的时间。但这种深度搜索也有问题,就是number有可能是无用的,下面的分析便是对这种深搜方法进行无用数据剪枝。
    值得注意的是,我们枚举过程中得到的number可能无用的,即无论用number去乘以多少,都无法得到区间内的数。这样的number如果继续枚举下去,无疑会大大降低效率。那么,能否通过简单的判断,将其剪去呢?答案是可以的。很容易证明,如果(min – 1) div number < max div number,则区间内存在可以被number整除的数。因为,如果区间[min, max]内存在可以被number整除的数,也即是从min到max中至少有一个数能被number整除,那么区间[min – 1, max]内的数被number除得的商肯定不止一种,所以(min – 1) div number必然小于max div number。
反过来,如果(min-1)div number=max div number,则[min,max]内不存在可以被number整除的数。
证明如下:
假设[min,max]内存在可以被number整除的数
如果(min-1) div number=max div number,则min div number = max div number,那么①min等于max,且min和max均可以被number整除,或者②max>min,min可以被number整除,①的情况下可推出,(min-1) div nunber<max div number,与条件矛盾,舍去;②的情况下也可以推出(min-1) div number<max div number,舍去。所以结论成立。因此,我们只需枚举符合要求的number;至于不符合的,可以剪去

    此外,我们枚举的质数可能会达到很大。因为给出的整数最大可以达到4,000,000,000,它的质因数自然最大也可以到100,000,000的数量级。如果按上面的方法枚举,显然无法承受时间的压力。但是,我们又可以看到,对于区间内的任一整数,它包含的大于SQRT(4,000,000,000) = 2*31623的质因数最多只可能有一个。因此,我们只需枚举小于2*31623的质数。如果对一个符合要求的number(即,可以证明区间内至少存在一个数可以被number整除),无法找到一个小于2*31623的质数p使得number * p * x ∈ [min, max](x为一正整数)。那么,可知number需要乘以一个大于31623的质数才能得到number’,使得number’ ∈ [min, max]。根据前面介绍的乘法原理,只需将number包含的约数个数乘以2,即得number’包含的约数个数。
    我们还能看到,如果当前搜索状态为(from, number, total),其中from是指当前枚举到的质因子(按从小到大枚举),total是指number中包含的约数个数。那么剩下的因子数最多为q = [log from(max / number)],这些因子组成的约数个数(即上述求约数个数时用到的一串乘积)最大为2 q。当前所能取到的(理想情况)最大约数个数就是total * 2 q,如果这个数仍然无法超过当前最优解,则这一分支可以剪去。
 
    深度搜索的过程,从表面上看是一个指数级的复杂度。其实不然,更准确的说,复杂度应为O(p logn)。因为,它的指数增长速度是随n成对数级增长,本质上说,还是多项式级的算法。而且我们还进行了一些剪枝,由于枚举中的分支多数为非法的,因此通过剪枝可以去掉绝大多数的分支。这样就大大提高了程序的效率。尽管数据规模很大,但对于极限数据仍然可以做到“一闪即出”。

法三:上述两法的中和方案。法一的思想,借用只不过用到的是法二中用筛法得到的质数表。这个更好理解一些

#include<stdio.h>
#include<string.h>
#include<stdlib.h>

#define MAX 100000

int prime[MAX];
int cnt;

//筛选法打印素数表
void Initprime(){
   bool get[MAXP+1];
	long i;
	for (i = 2; i <= MAXP; i++)
		get[i] = true;
	for (i = 2; i <= MAXP; i++)
		if (get[i])
		{
			long j = i + i;
			while (j <= MAXP) 
			{
				get[j] = false;
				j += i;
			}
		}
		long ii, j;
		for (ii = 2, j = 0; ii <= MAXP; ii++)
			if (get[ii]) prime[++j] = ii;
		cnt= j;
}

//求数m的因子个数
int Div( int m ){
    int tmp,ret=1;
    if( m<100000 && a[m]==0 )
        return 2;
    for( int i=0; prime[i]*prime[i]<=m && i<cnt; i++ ){
        if( m % prime[i] == 0 ){
            tmp = 0;
            while( m % prime[i] == 0 ){
                tmp ++;
                m /= prime[i];
            }
            ret = ret * ( tmp+1 );
        }
    }
    if( m != 1 )
        ret = ret * 2;
    return ret;
}

int main( ){
    int m, n;
    int max, ret;
    Initprime( );
    while( scanf( "%d%d", &m, &n ) != EOF ){
        max = 1;
        for( int i=m; i<=n; i++ ){
            ret = Div( i );
            if( ret > max )
                max = ret;
        }
        printf( "%d\n", max );
    }
    return 0;
}





  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值