Project_Euler-24 题解
题目
思路
如果是用笔算的话,可以找到这样的规律:
对于下面的这个组合:
0123456789 {0123456789} 0123456789
我们将其看作一种排列情况,然后我们先使用微扰的方式更改变动一下其中的数值,具体这样来做:
根据字典序的顺序,我们让他变动4次,记录每次变换的规律:
1:
01234567
9
8
01234567\textcolor {red}{9}8
0123456798
0123456 8 79 0123456\textcolor {red}{8}79 0123456879
0123456897 0123456897 0123456897
0123456 9 78 0123456\textcolor {red}{9}78 0123456978
发现一个这样的规律,越高位的数字,越难变动。最后一位数字每次都会变动,肯定是存在规律在其中的。
具体是什么样的规律呢?
假设一共有 N N N 位,如果我们想让倒数第 K K K 位增加1,那么从 K + 1 K + 1 K+1~ N N N位的数字位数都需要变换一遍,如果我们一位一位的确定,那么总共有如下的可能性:
C N − K 1 ∗ C N − K − 1 1 ∗ C N − K − 2 1 . . . C 1 1 C_{N-K}^{1} * C_{N-K-1}^{1}*C_{N-K-2}^{1}...C_{1}^{1} CN−K1∗CN−K−11∗CN−K−21...C11
例如:$012,
如果我们想让最高位变动,那么就需要
C
3
−
1
1
∗
C
3
−
2
1
=
C
2
1
∗
C
1
1
=
2
∗
1
=
2
C_{3-1}^{1}*C_{3-2}^{1} = C_{2}^{1}*C_{1}^1{} = 2 * 1 = 2
C3−11∗C3−21=C21∗C11=2∗1=2 次变动。
最终我们发现,如果想让第 K K K 位按照字典序变动一次,那么就需要让后面所有位变动 ( N − K ) ! (N-K)! (N−K)!位。
根据这个特点,我们可以判断出对于 0123456789 0123456789 0123456789 这串数列每一位想要变动时,后面需要变动的次数。
他们分别是:
9 ! , 8 ! , 7 ! , 6 ! , 5 ! , 4 ! , 3 ! , 2 ! , 1 ! , 0 {9!,8!,7!,6!,5!,4!,3!,2!,1!,0} 9!,8!,7!,6!,5!,4!,3!,2!,1!,0
现在题目想让我们求出第一百万位的的排列是什么,由于 0123456789 0123456789 0123456789 是第一种情况,因此我们实际需要变动 999999 999999 999999 次,我们可以通过上面的方式从最高位开始往后确定。
每一位应当尽可能的变化:
例如第一位, 9 ! = 352880 9! = 352880 9!=352880 所以可以让其变换 2 2 2 次,第一位的值就等于 2 2 2,此时 999999 − 2 ∗ 9 ! = 999999 − 725760 = 274239 999999 - 2 * 9! = 999999 - 725760 = 274239 999999−2∗9!=999999−725760=274239;
第二位, 8 ! = 40320 8! = 40320 8!=40320,可以让其变换 6 6 6 次,第二位的值就等于 6 6 6, 此时 274239 − 6 ∗ 8 ! = 32319 274239 - 6 * 8! = 32319 274239−6∗8!=32319;
依次类推。
值得一提的是,对于每一位变换的时候,初始值应该是多少呢?我们可以开辟一个数组,对每一位进行标记,如果使用了,就记为1,每用过就记为0,在变换时,我们从0开始,如果这个值被用过,那么向后跳一位,但是不计入变换次数。
代码
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <time.h>
#define MAX_N 10
// jump用来记录1到9的阶乘
int jump[MAX_N + 5];
// 用于标记哪些数字没被使用过, 1表示没被使用,0表示使用过了
int empty_num[MAX_N + 5];
// 初始化函数,计算阶乘,初始化empty_num数组
void init() {
jump[0] = 1, empty_num[0] = 1;
for (int i = 1; i <= MAX_N; i++) {
jump[i] = jump[i - 1] * i;
empty_num[i] = 1;
}
return ;
}
// 这个函数返回在需要变换k次时,第n位的值应该是多少。
int get_num(int n, int k) {
// ind记录应该变化的位数,i表示从-1开始
int ind = (k / jump[n]) + 1, i = -1;
while(ind) {
// i先加,从0开始判断
i++;
// 被用过的数字不会对ind造成影响
ind -= empty_num[i];
}
// 标记使用过
empty_num[i] = 0;
return i;
}
int main() {
init();
int n, k;
// n表示需要判断的位数,k表示要求的排列这里应该是一百万。
scanf("%d %d", &n, &k);
k -= 1;
// 从最高位开始判断
for (int i = n - 1; i >= 0; --i) {
int num = get_num(i, k);
printf("%d", num);
k %= jump[i];
}
printf("\n");
return 0;
}