例14 丑數
問題描述
丑數是其質因子只可能是2,3或5的數。前10個丑數分別為1, 2, 3, 4, 5, 6, 8, 9, 10, 12。輸入一個正整數n,求第n個丑數。
輸入格式
每行為一個正整數n (n <= 1500),輸入n=0結束。
輸出格式
每行輸出一個整數,表示求得的第n個丑數。
輸入樣例
1
2
50
0
輸出樣例
1
2
243
(1)編程思路。
根據丑數的定義,丑數從小到大排列的序列中的一個數應該是其前面某個數乘以2、3或者5的結果。因此,可以定義一個數組num[1501]來順序保存序列中的丑數,數組里面的每一個元素的值是前面的某個元素值乘以2、3或者5得到。
問題的關鍵是怎樣確保數組里面的各元素是按值的大小依次生成的。
假設數組中已經有若干個序列中的元素,排好序后存在數組中。把序列中現有的最大的數記做M。由於序列中的下一個數肯定是前面某一個數乘以2、3或者5的結果。首先考慮把已有的每個數乘以2。在乘以2的時候,能得到若干個結果小於或等於M的。由於數組中的元素是按照順序生成的,小於或者等於M的數肯定已經在數組中了,不需再次考慮;還會得到若干個大於M的結果,但只需要第一個大於M的結果,因為數組中的元素是按值從小到大順序生成的,其他更大的結果可以以后再說,記下得到的第一個乘以2后大於M的數M2。同樣,把已有的每一個數乘以3和5,記下得到的第一個大於M的結果M3和M5。那么,序列中下一個數應該是M2、M3和M5三個數的最小者。
事實上,上面所說的把數組中已有的每個數分別都乘以2、3和5,是不需要的,因為已有的數是按順序存在數組中的。對乘以2而言,肯定存在某一個數T2,排在它之前的每一個數乘以2得到的結果都會小於已有最大的數,在它之后的每一個數乘以2得到的結果都會太大。因此,只需要記下這個數的位置P2,同時每次生成一個新的序列中的數的時候,去更新這個P2。對乘以3和5而言,存在着同樣的P3和P5。
定義變量index保存當前待生成的數在序列中的序號,顯然,已生成的序列中的最大元素為Num[curIndex-1]。
定義3個指針變量int *p2,*p3,*p5;分別指向數組中的3個元素,排在所指元素之前的每一個數乘以2(或3、或5)得到的結果都會小於已有最大的數num[index-1],在所指元素之后的每一個數乘以2(或3、或5)得到的結果都會太大。
初始時,num[1] = 1、index =2、p2 = p3 = p5 = &num[1]。
生成第index個元素的方法為:
if (*p2 * 2<*p3 * 3) min = *p2 * 2;
else min= *p3 * 3;
if (min> *p5 * 5) min=*p5 * 5;
num[index] = min;
第index個元素生成后,需要對指針p2、p3和p5進行更新,更新方法為:
if(num[index]==*p2*2) p2++;
if(num[index]==*p3*3) p3++;
if(num[index]==*p5*5) p5++;
(2)源程序。
#include <stdio.h>
#include <string.h>
int main()
{
int num[1501],index,min,n;
int *p2,*p3,*p5;
p2=p3=p5=&num[1];
num[1]=1;
for (index=2;index<=1500;index++)
{
if (*p2 * 2<*p3 * 3) min = *p2 * 2;
else min= *p3 * 3;
if (min> *p5 * 5) min=*p5 * 5;
num[index] = min;
if(num[index]==*p2*2) p2++;
if(num[index]==*p3*3) p3++;
if(num[index]==*p5*5) p5++;
}
while(scanf("%d",&n) && n!=0)
{
printf("%d\n",num[n]);
}
return 0;
}
習題14
14-1 Hamming Problem
本題選自北大POJ 題庫 (http://poj.org/problem?id=2545)
Description
For each three prime numbers p1, p2 and p3, let's define Hamming sequence Hi(p1, p2, p3), i=1, ... as containing in increasing order all the natural numbers whose only prime divisors are p1, p2 or p3.
For example, H(2, 3, 5) = 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 16, 18, 20, 24, 25, 27, ...
So H5(2, 3, 5)=6.
Input
In the single line of input file there are space-separated integers p1 p2 p3 i.
Output
The output file must contain the single integer - Hi(p1, p2, p3). All numbers in input and output are less than 10^18.
Sample Input
7 13 19 100
Sample Output
26590291
(1)編程思路。
弄明白了例14的編程思想,本題就簡單了,無非是將例14中的質數2,3、5參數化為p1、p2和p3而已。
(2)源程序。
#include <stdio.h>
int main()
{
long long p1,p2,p3,H[100005];
int n,i,a1,a2,a3;
while(scanf("%lld%lld%lld%d",&p1,&p2,&p3,&n)!=EOF)
{
H[0] = 1;
a1=a2=a3=0;
for (i = 1;i<=n;i++)
{
H[i] = p1*H[a1]<p2*H[a2]?p1*H[a1]:p2*H[a2];
if (H[i]>p3*H[a3]) H[i]=p3*H[a3];
if(H[i]==p1*H[a1]) a1++;
if(H[i]==p2*H[a2]) a2++;
if(H[i]==p3*H[a3]) a3++;
}
printf("%lld\n",H[n]);
}
return 0;
}
14-2 丑數 Humble Numbers
本題選自洛谷題庫 (https://www.luogu.org/problem/ P2723)
題目描述
對於一給定的素數集合 S = {p1, p2, ..., pK},考慮一個正整數集合,該集合中任一元素的質因數全部屬於S。這個正整數集合包括,p1、p1*p2、p1*p1、p1*p2*p3...(還有其它)。該集合被稱為S集合的“丑數集合”。注意:我們認為1不是一個丑數。
將丑數集合中每個數從小到大排列,每個丑數都是素數集合中的數的乘積,第N個“丑數”就是在能由素數集合中的數相乘得來的(包括它本身)第n小的數。
你的工作是對於輸入的集合S去尋找“丑數集合”中的第N個“丑數”。
輸入格式
第 1 行: 二個被空格分開的整數:K 和 N,1<= K<=100 ,1<= N<=100,000。
第 2 行: K 個被空格分開的整數:集合S的元素。
輸出格式
單獨的一行,輸出對於輸入的S的第N個丑數。所有輸出答案可以用longint(32位整數)存儲。
輸入樣例
4 19
2 3 5 7
輸出樣例
27
(1)編程思路。
本題在習題14-1的基礎上更進一步,可以指定多個質數,這些質數來自集合S。因此在習題14-1的基礎上,用數組p[101]來代替p1、p2和p3,采用循環處理即可。具體參見源程序。
(2)源程序。
#include <stdio.h>
int main()
{
int p[101],a[101],H[100005];
int k,n,i,j;
scanf("%d%d",&k,&n);
H[0] = 1;
for (i=1;i<=k;i++)
{
scanf("%d",&p[i]);
a[i]=0;
}
for (i = 1;i<=n;i++)
{
H[i] = p[1]*H[a[1]];
for (j=2;j<=k;j++)
if (H[i]>p[j]*H[a[j]]) H[i]=p[j]*H[a[j]];
for (j=1;j<=k;j++)
if(H[i]==p[j]*H[a[j]]) a[j]++;
}
printf("%d\n",H[n]);
return 0;
}
14-3 集合S
問題描述
一個集合S有如下元素:1是集合S的元素;若x是集合S的元素,則2x+1,3x+1也是集合S的元素。將集合S的元素按從小到大排列后,問第N個元素是多少?
輸入格式
輸入包含多個數據,每個數據為一個正整數N (1 <= N <= 10000000)。.
輸出格式
對每個N,在單獨一行輸出集合S中第N個元素。
輸入樣例
100
254
輸出樣例
418
1461
(1)編程思路。
這道習題和例14的解題思路是完全一樣的。兩個指針p2和p3從0開始起頭並進,一個表示 x2+1,另一個表示x3+1,哪個小前進哪個,如果兩個相等就都前進。這樣就可以產生出遞增的不重復的序列。
定義數組int num[10000001]保存產生的這個序列的前10000000項,這樣輸入n后,直接輸出數組元素num[n]即可。
(2)源程序。
#include <stdio.h>
int num[10000001];
int main()
{
int p2,p3,i,min,n;
num[1]=1;
p2=p3=1;
i=1;
while(i<10000000)
{
min=2*num[p2]+1;
if (min>3*num[p3]+1) min=3*num[p3]+1;
num[++i]=min;
if(num[i]==2*num[p2]+1) p2++;
if(num[i]==3*num[p3]+1) p3++;
}
while (scanf("%d",&n)!=EOF)
{
printf("%d\n",num[n]);
}
return 0;
}