Problem Description
Consider the aggregate An= { 1, 2, …, n }. For example, A1={1}, A3={1,2,3}. A subset sequence is defined as a array of a non-empty subset. Sort all the subset sequece of An in lexicography order. Your task is to find the m-th one.
Input
The input contains several test cases. Each test case consists of two numbers n and m ( 0< n<= 20, 0< m<= the total number of the subset sequence of An ).
Output
For each test case, you should output the m-th subset sequence of An in one line.
Sample Input
1 1
2 1
2 2
2 3
2 4
3 10
Sample Output
1
1
1 2
2
2 1
2 3 1
题意解析:
给出一个1~N的集合,将这个集合的所有子集按照字典序排列,输出第m个集合。
题解:
这道题是一道数学题,但是技巧性挺高的。
首先很容易想到子集的排列是有规律的,但是并不能按照顺序去找到第m个。
先看一组数字:
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
可以看出这些数字是分节的,而且每一节的数目是可以递归推导出来的。
设f(n)表示当总数n个时一共有多少个子集;
g(n)表示当总数n个时每一节有多少个子集;
则:
f(n) = n*( f(n-1)+1 );
g(n) = f(n)/n = f(n-1)+1;
f(n-1) = g(n-1)*(n-1);
g(n) = g(n-1)*(n-1) + 1;
(注意数据可能会很大,需要long long)
这样,我们可以迭代求出每个n对应的小节大小。
下一步:注意对于每个n对应的第i个小节的数为当前该集合的第i个数字,这句话的意思是每个小节对应的数字是固定的,比如上面例子中的第2个小节,对应第一个数字一定是当前集合{1,2,3}的第2个数字2,而再看次小节,即{2}~{2,3,1},除去第一个空集,它的第二个小节对应的第一个数字一定是当前集合{1,3}(因为2已经被选了)的第2个数字3。
所以看出上面的过程,那么我们现在要解决的问题就转化为:
1.如何删去一个数字;2.如何递归进入的下一层次集合;
第一个问题我们可以通过设置一个数组num,num[i]表示当前数组的第i个数字,这样最开始的时候num[i]=i,当删去一个数字x时,num[x]=num[x+1],这样及时更新当前集合。
第二个问题可以利用while循环解决,既然g(n)已经算出来,那么递归的去找次子集也应该会很简单,需要注意每个小节的第一个空集。
代码实现:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#define MAX 22
#define LL long long
using namespace std;
int num[MAX]; //储存当前集合,实时更新
LL save[MAX]; //保存每个小节的大小
int main()
{
int N;
LL M; //M也需要设置为LL
memset(save,0,sizeof(save));
for( int i = 1; i < MAX; i++ ){
save[i] = save[i-1]*(i-1)+1;
}
while( scanf("%d%I64d",&N,&M) != EOF ){
//每一次都将集合刷新
for( int i = 0; i <= N; i++ ){
num[i] = i;
}
while( M > 0 && N > 0 ){
//定位到第tmp个小节
int tmp = M/save[N] + (M%save[N]==0 ? 0 : 1);
if( tmp > 0 ){
//删去之前的元素
M -= save[N]*(tmp-1);
//输出当前集合的第tmp个数值
printf("%d",num[tmp]);
//刷新集合,将已经输出的数字覆盖
for( int i = tmp; i < N; i++ ){
num[i] = num[i+1];
}
//删去每个小节开头的空集
M--;
if( M == 0 ){
printf("\n");
}
else{
printf(" ");
}
}
//进入次小节
N--;
}
}
return 0;
}