HD 2062(子集的字典序排列)

                                               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;
} 




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值