Subset sequence
Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 5909 Accepted Submission(s): 2784
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
思路
先讨论n=3,m=10的情况,看能不能发现什么:
n=3时原集合为{1,2,3},非空子集有{1},{2},{3},{1,2},{1,3},{2,3},{1,2,3}
字典排序后产生的子集有15个:
我们发现按字典序排列后,如果按开头元素从小到大分组,恰好可以分成n个相同子集个数的组合。
并且可以发现每个分组除第一列元素后剩下的子集对应n-1时所有的集合数。
于是,可以先利用上述的递推关系求得当n取1~20时,每种情况的组中集合数,打好表。用g[i]表示不同n下分组中集合的个数: g[i]=g[i-1]*(i-1)+1;
并用first[i]储存开头元素。
然后根据m可以用 t 定位所求集合在第几组,打印第一个元素后缩小问题规模n- -, 改变m,根据新的n,m从新定位t
最关键的一步是每进行完一次打印后,不能直接进行下一次,因为虽然种数是对应的,但是元素不对应。
为什么会产生不对应呢?(决定答打印首元素下标的是 t )因为前面打印好的数字消失了,不可能再打印了,如果表first[i]不变,之后可能还会打印重复的数字,于是对first[i]进行处理:可以发现如果后面要打印的首元素小于之前的首元素,结果不会重复;但是若大于,t在找first时要进行+1处理,于是在每次打印后,将first中t到n之间开头元素+1。
for(int i=t;i<=n;++i) first[i]=first[i+1]。
比如,n=3,m=10,首先t定位到第二组(t=2),打印此组开头元素,然后对t(2)到n(3)之间的开头元素+1处理。
然后n=2,m=4,t=2(说明问题规模缩小后,原先第十个排列现在定位到第四个):
1
1 3
3
3 1(所求)
现在t定位到第二组(t=2)({3}, {3,1}),打印头元素,first[t] ,因为之前处理了原本是2(前面打印了,要改变)现在改为3了(正确排列)。first[2]变为4(之后用不到了)
最后n=1,m=1; t=1;打印first[1], 即1 , 换行,完成任务。
代码示例
#include<iostream>
#include<cstdio>
#include<string>
using namespace std;
typedef long long LL;
int n,t;//n为一共能分多少组(即题意的n),t为所求子集所在分组的次序(比如n=3时,t=2)
int first[21];//每一个分组的初始元素
LL m;//m为总共第几个子集 (即题意的m)
LL g[21]={0};//当n从1到20时,所分组的组里的子集个数(递推解决)
void set_table()//打好表
{
for(int i=1;i<21;++i){
g[i]=g[i-1]*(i-1)+1;
}
}
int main()
{
set_table();
while(cin>>n>>m)
{
for(int i=0;i<21;++i){
first[i]=i;
}
while(n>0&&m>0)
{
t=m/g[n]+(m%g[n]>0?1:0);//定位所求子集所在组序号
if(t>0){
cout<<first[t];
for(int i=t;i<=n;++i) first[i]=first[i+1];
m-=((t-1)*g[n]+1);//更新m
putchar(m==0?'\n':' ');//m为0结束换行否则空格
}
n--;//缩小问题规模
}
}
return 0;
}