ACM杂题I——不知什么解法就是解出来的题解(应该不是递归)

ACM杂题I——Subset sequence题解

题目描述

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共n个数的组成的集合,求出其非0子集,并将所有子集按照字典序排列,打印出第m个。

 

题解

先观察下n个数组成的子集的排列规律

n=1     1         n=2       1                n=3    1                                          

                                   1  2                      1  2                                 

                                   2                          1  2  3

                                   2  1                      1    

                                                               1  3  2

                                                               2……   

                                                               3……                     观察可以发现子集个数的递归关系为   a_n=n\ast \left ( a_n_-_1+1\right )  ,a_1=1

An表示第n个集合,an表示第n个集合的非0子集个数

所以可以看出An是可以分为n组,每组的第一位分别是1~n,因此第m个数组的首位可以通过m/an-1来确定,现在相当于在大组中找到了m属于哪一个小组;接下来在这个小组内部,可以发现除去第一位,他的其余位也是按照从小到大的子集字典序排列的,因此可以发现,可以将m/an-1的余数m%an-1,减去1再除以an-2,求出“第二位”,而这个第二位不单纯就是(m%an-1)/an-2,而是删除第一位后An剩余几位从小到大排序的第m%an-1/an-2位,从而求出了m位于该小组的第几小小组,一直这样求下去直到((m%an-1)%an-2    -1)%……等于0,停止,说明求出了所有位。

n=1     1         n=2       1                n=3    1                                          

                                   1  2                      1  2                                       

                                   2                          1  2  3                                                                        

                                   2  1                                   第4行

                                                               1  3  2

                                                               2……   

                                                               3……

减一的来源是这样的: 在大组中是第m行,在小组中因为去掉了高位的那一行,所以在小组中为第 (m%an-1) -1行 

举一个栗子说明     

n=3   m=14

An={1,2,3}

将子集个数打表方便以后每一次可以使用,减少时间复杂度,从c[1]~c[n]=1,4,15,64……

第一位   14/5=2···4, 为An[2]=3;   在An中将3删去, An={1,2};

第二位   4-1/2=1···1,为An[1]=2;   在An中将2删去,An={1};

算第三位时  1-1/1=0,停止计算

因此输出为 3  2。

 

代码

#include<iostream>
#include<stdio.h>
#include<vector>
using namespace std;
int main()
{
    long long int m=0,n=0;
    long long int a[21]={0};
    a[0]=2;
    a[1]=1;
    for(long long int i=2;i<21;i++)
        a[i]=i*(a[i-1]+1);              //递归打表a
    while(scanf("%lld%lld",&n,&m)!=EOF)
    {
        long long int *b=new long long int[n];
        vector<long long int>c;
        for(int i=1;i<=n;i++)
        {
            c.push_back(i);             //输入集合C
            b[i-1]=0;                   //初始化数组b,用于存放要打印的结果
        }
        long long int nm=m,nc=m;
        for(long long int i=1;(i<=n && nc!=0);i++)
        {
            long long int index=0;      //存放算出的是集合C中的第几位
            if(nm%(a[n-i]+1)==0)
                index=nm/(a[n-i]+1)-1;  //整除的时候需要后移一位
            else
                index=nm/(a[n-i]+1);    //除以n-1的个数加1为每一小组的个数
            b[i-1]=c[index]; 
            c.erase(c.begin()+index);   //集合更新
            nc=((nm-1)%(a[n-i]+1));
            nm=nc;
        }
        for(int i=0;i<n && b[i]!=0;i++) //打印结果
        {
            if(i==n-1 || b[i+1]==0)
                cout<<b[i];
            else
                cout<<b[i]<<" ";
        }
        cout<<endl;
    }
    return 0;
}

 

 

                                                          

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值