HDU_2062 Subset sequence(math)

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

题目链接点我
参考博客点我

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值