请教最多约数问题

lx010577 2010-04-15 01:25:40
问题描述:
正整数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
---------------------------------------

方法:设正整数x的质因子分解为
x=p1^N1 × p2^N2 ×……pi^Ni
则 div(x)=(N1+1)(N2+1)……(Ni+1)。
程序运行正确,效率也行,就是下面用红色标注的语句有些不明白,还请大侠们多指教……
Environment: MS VC6.0
#include<iostream>
using namespace std;

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;
return 0;
}


void primes() //执行完后: prim[]={2, 3, 5, 7, 11, 13……}
{
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;
}

// 区间[low,up]上,tot为当前约数最多个数,num为约数个数最多的数,
//from表示现在是第几个质数。
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;

}//end else
}//end for
}
...全文
1220 20 打赏 收藏 转发到动态 举报
写回复
用AI写文章
20 条回复
切换为时间正序
请发表友善的回复…
发表回复
hayes_shi 2012-10-09
  • 打赏
  • 举报
回复
不是很明白呀!
xiaokimi5 2012-09-17
  • 打赏
  • 举报
回复
最优化减枝,没有看懂,什么意思啊?
cywyw520 2010-11-18
  • 打赏
  • 举报
回复
[999999999, 1000000000] 56
你的代码输出的是70啊 不是56? wa的
black_1890 2010-09-09
  • 打赏
  • 举报
回复
【算法分析】

本题的要求是,求出一个给定区间内的含因子数最多的整数。

首先,有必要明确一下如何求一个数的因子数。若一个数N满足N=P1N1·P2N2·P3N3·…·PmNm,其中P1, P2, …, Pm是N的m个质因子。则N的约数个数为(N1+1)·(N2+1)·(N3+1)·…·(Nm+1)。这一公式可以通过乘法原理来证明。

有了求因子数的公式后,最容易想到的算法就是,枚举区间内的每个整数,统计它们的约数个数。这个算法很容易实现,但是时间复杂度却相当高。因为区间中整数的范围是1~1000000000,整个枚举一遍并计算因子数的代价约为109×(109)0.5=1013.5。这个规模是无法忍受的。所以,我们需要尽量优化时间。

分析一下枚举的过程就会发现,如果我们分别枚举两个数n和p·n(p为一相对较大的质数),那么我们将重复计算两次n的因子数。其实,如果枚举顺序得当的话,完全可以在n的基础上去计算p·n,而如果能在n的基础上计算p·n,就相当于计算p·n的因子数只用了O(1)的时间。这是一个比较形象的例子,类似的(可能相对更复杂一些)重复计算在枚举过程中应该是普遍存在的。这就是枚举效率低的根本所在。为了解决这一重复,我们可以选取另一种搜索顺序——枚举质因子。这样的搜索顺序可以避免前面所说了类似n和p·n的重复计算。

定义number为当前搜索到的数。初始时,令number=1,然后从最小的质数2开始枚举,枚举因子中包含0个2、1个2、2个2、…、k个2的情况……直至number·2k大于区间的上限(max)。对于每个“2k的情况”,令number:=number*2k,在这个基础上,再枚举因子3的情况。然后在3的基础上枚举因子5的情况,然后是7的情况……整个过程是一个深度搜索的过程,搜索的过程中,利用前面提到的求因子数的公式可以算出当前的number的因子数供下一层枚举继承。当number大于等于区间下限(min)时,我们就找到了一个区间内的数(枚举的过程已保证number不超过上界)。所有枚举得到的区间内的数中,因子数的最大值就是我们要求的目标。

这样的枚举完全去除了重复计算,但是这还是不够的,因为光1~1000000000内的数每枚举一遍就有109个单位的操作。所以,我们还需要找到一些剪枝的方法,进一步优化时间。

我们看到,如果当前搜索状态为(from, number, total),其中,from是指当前枚举到的质因子(按从小到大枚举),total是指number中包含的因子数。那么剩下的因子数最多为q=logfrommax/number,这些因子组成的因子个数最大为2q。因此,当前所能取到的(理想情况)最大约数个数就是total·2q。如果这个数仍然无法超过当前最优解,则这一分支不可能产生最优解,可以剪去。

此外,如果[(min-1)/number]=[max/number],则表示以当前状态搜索下去,结果肯定不在区间内了,就无法产生合法解,也可剪去。不过,这一剪枝作用不是很大,因为即使不剪,再搜索一层也就退出了。

以上两个剪枝,前一个是最优化剪枝,后一个是合法性剪枝。相比较而言,前一个剪枝的作用要大得多。

下面我们用平摊分析的方法来讨论一下搜索的复杂度。由于枚举的过程中没有重复计算,每枚举一个质因子,都可以得到一个不同的number(number≤max),所以可以将每一个单位的枚举质因子的代价与一个不超过max的number对应,并且还可在两者之间建立双射。不同的number最多只有max个,所以枚举的总代价不超过O(max),max≤109。

加上了剪枝以后,计算总代价就远远小于O(max)了。从运行效果来看,即便是最大数据,也可以很快出解。

从本题的解决过程中可以看到,最关键的有两步:

(1)采用合理的搜索顺序,避免重复计算;

(2)利用最优化剪枝和合法性剪枝,剪去一些不可能产生最优解或合法解的分支。

这两种优化的方法在搜索中的地位是极其重要的,当然可能在本题中的重要性体现得格外突出。
zsxcn 2010-04-30
  • 打赏
  • 举报
回复
mark
michael122 2010-04-16
  • 打赏
  • 举报
回复
至于你说的公示的问题,是通过这句来实现的:t += tot;
t相当于是(N1+1)(N2+1)……(Ni+1)*m tot=(N1+1)(N2+1)……(Ni+1)
michael122 2010-04-16
  • 打赏
  • 举报
回复
在while循环里:

while (true)
{
m++;
t += tot;
x /= j;
y /= j;
if (x == y) break;
n *= j;
search(i+1, t, n, x+1, y);
}

每一次的循环相当于寻找 ...j^(m-1)... 这样的分解,把所有可能的m都考虑到了,也就是你说的同一个素数连续出现多次的情况了,唯一例外的是分解不带j的情况,这是通过进入下一步的for循环来实现的
XLL19880206 2010-04-16
  • 打赏
  • 举报
回复
这不是在王晓东那本算法书上有的吗?
lx010577 2010-04-16
  • 打赏
  • 举报
回复
[Quote=引用 3 楼 michael122 的回复:]

C/C++ code

这个函数的作用是:已知num有tot个因子,并且原区间为[num*low,num*up],求从from个素数开始,原区间内的数可以分解到的最多的约数
void search(long from, long tot, long num, long low, long up)
{
if (num >= 1)
if ( (to……
[/Quote]

感觉你的理解很有帮助,谢谢你。
我的理解是程序是通过质因子({2, 3, 5, 7, 11, 13……})来找满足条件的数
(通过判断质因子的乘积是否小于上届(up))。
比如说 在3到40之间,
通过递归,可以发现2*3*5<40;可以知道30至少有3个因子。
但类似36=2*2*3*3这样 有连续2个2和连续2个3的程序怎么找到呢?
注意到for循环里,search(i+1;t,n,x+1,y);
search的第一个参数是i+1,也就是递归后将从当前质数的下一个质数开始找,也就是下一个循环内的j要比当前的j大1.j->j+1
这样怎么可以发现连续的两个质数呢?

另外,如果结合公式
x=p1^N1 × p2^N2 ×……pi^Ni
则 div(x)=(N1+1)(N2+1)……(Ni+1)。
,程序该怎么理解呢?
lx010577 2010-04-16
  • 打赏
  • 举报
回复
[Quote=引用 7 楼 renzhewh 的回复:]

给定一个数n ,从1 开始查找 至 n 的平方根,若 i是n 的因数,那么 n/i 也为 n 的因数
下面的函数用于统计一个正数的因数的个数

C/C++ code

int count(int n)
{
int i = 1;
int res = 0;

for ( ; i * i <= n; ++i)
{
if (n % i == 0)
……
[/Quote]

这个算法复杂度挺高吧,对于很大的数不大好吧
强风 2010-04-16
  • 打赏
  • 举报
回复
[Quote=引用 14 楼 michael122 的回复:]

至于你说的公示的问题,是通过这句来实现的:t += tot;
t相当于是(N1+1)(N2+1)……(Ni+1)*m tot=(N1+1)(N2+1)……(Ni+1)
[/Quote]

我看了半天还是理解不了饿!
gysgogo 2010-04-16
  • 打赏
  • 举报
回复
郁闷,没看题
gysgogo 2010-04-16
  • 打赏
  • 举报
回复
我的书上正好有这个题,下面是书上的原内容:
设正整数x的因子分解为:x=p1^N(1)*P2^N(2)*.......PN^N(k)
则 搜索[a,b] 中数的质因子分解.
primes产生质数
void primes(){
bool get[Max+1];
for(int i=2;i<=maxp;i++) get[i]=true;
for(int i=2;i<Maxp;i++)
if(get[i]){
int j=i+1;
while(j<Maxp){get[j]=false;j+=i;}
}
for(int ii=2,j=0;ii<maxp;i++)
if(get[ii]) prim[++j]=ii;
}
void search(int form,int tot,int num,int low,int up){
if(num>1)if(tot>max||((tot==max)&&(num<numb))){
max=tot;numb=num;
}
if((low==up)&&low>num) search(form,tot*2,num*low,1,1)
for(int i=form;i<pcount;i++){
if(prim[i]>up) return;
else{
int 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;
}
}
}
int main(){
primes();
cin>>1>>u;
if(1==l&&u==1){max=1,numb=1}
else{max=2;numb=1;search(1,1,1,1,u)}
cout<<max<<endl;
return 0;
}
写的冲忙 可能与原文有些出入;
renzhewh 2010-04-16
  • 打赏
  • 举报
回复
给定一个数n ,从1 开始查找 至 n 的平方根,若 i是n 的因数,那么 n/i 也为 n 的因数
下面的函数用于统计一个正数的因数的个数


int count(int n)
{
int i = 1;
int res = 0;

for ( ; i * i <= n; ++i)
{
if (n % i == 0)
res += 2;
}
if ((i - 1) * (i - 1) == n)
res -= 1;
return res;
}
xiuxianshen 2010-04-15
  • 打赏
  • 举报
回复
[Quote=引用 5 楼 xiuxianshen 的回复:]
引用 4 楼 xiuxianshen 的回复:
楼主其实只要做[b/a*a,b]即可(a,b都是整数,int或者long)

错了,简单点吧
if (a < b/2) 就做[b/2,b]
else 做[a,b]
[/Quote]
想错了,忽略吧
xiuxianshen 2010-04-15
  • 打赏
  • 举报
回复
[Quote=引用 4 楼 xiuxianshen 的回复:]
楼主其实只要做[b/a*a,b]即可(a,b都是整数,int或者long)
[/Quote]
错了,简单点吧
if (a < b/2) 就做[b/2,b]
else 做[a,b]
xiuxianshen 2010-04-15
  • 打赏
  • 举报
回复
楼主其实只要做[b/a*a,b]即可(a,b都是整数,int或者long)
michael122 2010-04-15
  • 打赏
  • 举报
回复

这个函数的作用是:已知num有tot个因子,并且原区间为[num*low,num*up],求从from个素数开始,原区间内的数可以分解到的最多的约数
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);
这句其实可以去掉,只是为了提高效率,当满足条件时,可以预见tot至少可以达到2倍的tot,且num*low就是这个数,这里的调用只是更新max和numb的值
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) //区间里数的分解可以分为带j的和不带j的,这里处理带j的情况
{
m++;
t += tot;
x /= j;
y /= j; //这里每除一次j,表示分解出一个j来
if (x == y) break; //说明区间[x,y]中的数没有j的倍数了
n *= j;
search(i+1, t, n, x+1, y); //带j的分解又可以按j的次数来分类,这里m-1为当前分解中j的次数,然后递归调用
}
m = 1 << m;
if (tot < max / m) return; //这里处理分解不带j的,进行下一次循环前先估计一下当前的分解有没有可能达到已经求得的最优解,否则没必要继续下去
}//end else
}//end for
}







以上是我的理解,不知道正不正确
tanggui2007 2010-04-15
  • 打赏
  • 举报
回复
难道把一个数因式分解很容易吗?
lx010577 2010-04-15
  • 打赏
  • 举报
回复
ZIJI自己顶一下

33,010

社区成员

发帖
与我相关
我的任务
社区描述
数据结构与算法相关内容讨论专区
社区管理员
  • 数据结构与算法社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧