HDU 2062:Subset sequence

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2062



题目翻译:给出一个N代表有一个集合,集合有1~N中的数字组成。集合的所有子集,

非空集,按照字典序排序,给出一个M,求第M个集合的元素。


解题思路:数学找规律。

N = 1     其集合排序如下

{1}

-------------------------------------------------------------------------------

N = 2                    其集合排序如下

{1}

{1,2} 


{2}

{2,1}

-------------------------------------------------------------------------------

N = 3          其集合排序如下

{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个数的集合S,其子集个数可以这样计算。

从N个数中选择1个,然后进行全排列    C(n,1)*A(1,1)

从N个数中选择2个,然后进行全排列    C(n,2)*A(2,2)

从N个数中选择M个,然后进行全排列   C(n,m)*A(m,m)    (M<=N)

则N个元素的集合共有sum =  C(n,1)*A(1,1) +  C(n,2)*A(2,2) + .......+ C(n,m)*A(m,m) 

则为了确定第一个元素,我们可以用sum确定出第一个数字所在的组,然后可以进行取余操作

确定是某组的第几个,然后这个问题就变成了刚才问题的子问题,按照该方法继续解决就可以

正确求解。

题目易错点:输入的M会很大,所以要用long long ,刚开始没注意到M用的是int类型,提交一直

TLE。原因是当M溢出的时候,其值为负数,无法结束我程序中写的循环。


AC代码:

#include <iostream>
#include <stdio.h>
#include <string.h>

using namespace std;
int ans[50];
long long power[30];
long long num[30];  //num[i]用来记录N个数的时候有多少个子集
int vis[30];
int cnt;
void solve(int N,long long M) {
    cnt = 0;
    memset(vis,0,sizeof(vis));
    int rest = N;
    while(true) {
         //cout<<"M = "<<M<<endl;
         int temp = M/num[rest];   ///计算答案在第几组
         if(M%num[rest] != 0)
            temp++;
         int ccount = 0;
         for(int i = 1; i <= N; i++) {
            if(vis[i]==0) {
                ccount++;
                if(ccount == temp) {
                    vis[i] = 1;
                    ans[cnt++] = i;
                    rest--;       ///数字减少一个
                    break;
                }
            }
         }
         M = M-(temp-1)*num[rest+1];   ///计算它是该组的第几个
         M--;
         if(M == 0)
            break;
    }
}
int main() {
    int N;
    long long M,Mul = 1;
    power[0] = 1;
    for(int i = 1; i <= 20; i++) {
        power[i] = Mul*i;
        Mul = Mul*i;
    }

    for(int i = 1; i <= 20; i++) {
        num[i] = 0;
        for(int j = 1; j <= i; j++) {
            num[i] = num[i] + power[i]/power[i-j];
        }
        num[i] = num[i]/i;
    }
    while(~scanf("%d%lld",&N,&M)) {
        solve(N,M);
        printf("%d",ans[0]);
        for(int i = 1; i < cnt; i++) {
            printf(" %d",ans[i]);
        }
        printf("\n");
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值