题目
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
思路
对于An的个数,显然是存在规律的。
- n = 1 时,只有{1}一个子集
- n = 2 时,有如下四个子集
{1} {1,2}
{2} {2,1} - n = 3时,有如下15个子集。
{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} - n = k时,…
进一步对其剖析,将子集分组。n=2可以分为2组,n=3可以分为3组,…,n = k就可以分k组。这个分组显然是根据第一个元素来区分的。找到这个规律就好办,当n = k时,我把第一个元素确定好后,剩下k-1个元素的确定不就是n = k-1时的情景。因此我们就得出求子集个数的递推关系式如下
n u m ( k ) = k ∗ [ n u m ( k − 1 ) + 1 ] 其 中 n u m ( 1 ) = 1 num(k) = k*[num(k-1)+1] \\其中num(1) = 1 num(k)=k∗[num(k−1)+1]其中num(1)=1
公式中num(k)代表n=k是,包含子序列个数,里面加了一个1是因为存在一种情况,即{k}单独存在,不包含任何k-1的子序列这种情况,因此加了个1。
上面的描述,其实就有点动态规划的影子了,但我们这题的关键不在于求解子集个数,而是要根据这些关系求出第m个排序的具体序列。显然根据之前的分组来说,我们可以先得出m在哪一组,确定了在哪一组后,其输出序列第一位P1就确定了。
根据关系我们不仅可以求出它在第几组,还可以求出它在第几组第几个。显然我们可以据此采用重复的上述步骤求解下一个P2,此后依次求解其他…
下面以一个例子说明。
例子
求n=3,m=10时的输出序列
可以看出一共存在3个组,每个组的个数num_perGroup现在成为我们需要掌握的。显然
n
u
m
_
p
e
r
G
r
o
u
p
(
k
)
=
n
u
m
(
k
)
/
k
num\_perGroup(k) = num(k)/k
num_perGroup(k)=num(k)/k,下面我们推到k和k-1每组个数的关系:
∵ n u m ( k ) = k ∗ [ n u m ( k − 1 ) + 1 ] num(k) = k*[num(k-1)+1] num(k)=k∗[num(k−1)+1]
∴ n u m _ p e r G r o u p ( k ) = k ∗ [ n u m ( k − 1 ) + 1 ] / k = n u m ( k − 1 ) + 1 num\_perGroup(k) = k*[num(k-1)+1]/k =num(k-1)+1 num_perGroup(k)=k∗[num(k−1)+1]/k=num(k−1)+1
∵ n u m ( k − 1 ) = n u m _ p e r G r o u p ( k − 1 ) ∗ ( k − 1 ) num(k-1) =num\_perGroup(k-1)*(k-1) num(k−1)=num_perGroup(k−1)∗(k−1)
∴ n u m _ p e r G r o u p ( k ) = n u m _ p e r G r o u p ( k − 1 ) ∗ ( k − 1 ) + 1 num\_perGroup(k) = num\_perGroup(k-1)*(k-1)+1 num_perGroup(k)=num_perGroup(k−1)∗(k−1)+1
最后一个公式即是相邻两者每个组包含子集个数的关系。显然num_perGroup(1) = 1,num_perGroup(2) = num_perGroup(2-1)×1+1 = 2,同理num_perGroup(3) = 2×2+1 = 5。
- round1:对于m=9,显然它在第2组,第4个数,因次可以确定P1=2。
- round2:输出第一个数2后,还剩1,3两个数,显然现在情况更新为n=2,m=4-1=3这种情况(这里-1是为了排除只输出一个2就没有了这种情况,若减了后为0,那就输出序列仅为2,否则继续输出)。显然它在第二组,第一个数。而剩下的两个数中,第二组即是开头为3的情况,因此可以确定P2=3。
- round3:输出第二个数3后,只剩1这个数,他的情况继续更新为n=1,m=1-1=0(依旧减了一个1).显然它成了我们之前说的输出为空的情况,这种情况即不输出。
- 综上序列只有两位即2 3。
代码
#include <stdio.h>
int main() {
long long num_perGroup[21],m;//num_perGroup:下标i中每组的个数,比如num_perGroup[3]=5
int n,i,group,print_num[21];//print_num是根据计算出的位置打印数
num_perGroup[1] = 1;
for (i = 2; i <= 20 ; ++i)//根据前文推理的公式,初始化每组个数
num_perGroup[i] = (i-1) * num_perGroup[i - 1] + 1;
while (scanf("%d%lld",&n,&m)!=EOF){
for (i = 1; i <=20 ; ++i)//初始化打印数,打印第i个位置就是i,随着输出进行,其会发生变化
print_num[i] = i;
while (m){//当m更新为0时就不需要再打印即退出
group = (m-1)/num_perGroup[n] + 1;//计算当前打印数,即计算为第几组,
printf("%d",print_num[group]);//根据组号打印
for (i = group; i < 20 ; ++i)//打印后,把打印完的覆盖,之后不会在打印
print_num[i] = print_num[i+1];
m = m % num_perGroup[n];//计算在组内的位置
if(m==0)//在最后一个位置
m = num_perGroup[n];
m--;//去除第一个不要任何数跟着的情况比如{3}{2}{1},这在后续迭代中很重要,否则会计算组数错误。
n--;//打印出了一个,因此n要减去一个
if(m)//判断是否打印完
printf(" ");
else
printf("\n");
}
}
}
参考
思路来源:https://blog.csdn.net/a939682389/article/details/79273354