我是把它当做一道数学题来做的。
这篇题解写的有点啰嗦,但是是我最原始的思维过程。
对于一个集合An= { 1, 2, …, n },在n比较小的情况下,在纸上按字典顺序把所有子集排列一下。
以n=3,m=10举例:
1 1 2 1 2 3 1 3 1 3 2 2 2 1 2 1 3 2 3 2 3 1 3 3 1 3 1 2 3 2 3 2 1
容易看出前5个打头的是1,紧接着5个子集打头的是2,最后5个开头的是3。
拿前五个来说,除了第一个,后面四个不看开头的1,后面的排列形式和n=2的子集的排列很相似。
f(n)代表集合An所有子集的个数,那么有递推关系:
f(n) = n * (f(n - 1) + 1), f(1) = 1
这里数组taken的作用就是标记某个数是否被占用。
在这个例子里面,要求第一个数,计算(10 - 1) / 5 + 1 = 2。
表示这个数是所有未被占用的数里面从小到大第2个数,也就是2。
再计算一下余数r = (10 - 1) % 5等于4
如果r == 0说明后面的数没有了,跳出循环。
否则m = r;
继续下一轮循环
这里m == 4,计算第二个数 (4 - 1) / 2 + 1 == 2。
现在2已经被第一个数占用了,所以未被占用的第二个数就是3。
后面依次类推。
1 //#define LOCAL 2 #include <iostream> 3 #include <cstdio> 4 #include <cstring> 5 using namespace std; 6 7 int main(void) 8 { 9 #ifdef LOCAL 10 freopen("2062in.txt", "r", stdin); 11 #endif 12 13 int n; 14 bool taken[25]; 15 int b[25]; 16 long long m, a[25]; 17 a[1] = 1; 18 for(int i = 2; i <= 20; ++i) 19 a[i] = i * (a[i - 1] + 1); 20 21 while(scanf("%d%I64d", &n, &m) == 2) 22 { 23 memset(taken, false, sizeof(taken)); 24 int i; 25 long long r = 1; 26 for(i = 1; i <= n; ++i) 27 { 28 b[i] = ((m - 1) / (a[n - i] + 1)) + 1; 29 int j, k = 0; 30 for(j = 1; j <= n; ++j) 31 { 32 if(!taken[j]) 33 ++k; 34 if(k == b[i]) 35 break; 36 } 37 b[i] = j; 38 taken[j] = true; 39 r = (m - 1) % (a[n - i] + 1); 40 if(r == 0) 41 break; 42 m = r; 43 } 44 for(int j = 1; j < i; ++j) 45 printf("%d ", b[j]); 46 printf("%d ", b[i]); 47 } 48 return 0; 49 }